Skip to content

ajayk/fosite

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fosite security first OAuth2 framework

The security first OAuth2 framework for Google's Go Language. Built simple, powerful and extensible. This library implements peer-reviewed IETF RFC6749, counterfeits weaknesses covered in peer-reviewed IETF RFC6819 and countermeasures various database attack scenarios, keeping your application safe when that hacker penetrates or leaks your database.

Build Status Coverage Status

Be aware that go get github.com/ory-am/fosite will give you the master branch, which is and always will be nightly. Once releases roll out, you will be able to fetch a specific fosite API version through gopkg.in. As of now, no stable v1 version exists.

During development, we reviewed the following open specifications:

Table of Contents

Motivation

Fosite was written because our OAuth2 and OpenID Connect service Hydra required a secure and extensible OAuth2 library. We had to realize that nothing matching our requirements was out there, so we decided to build it ourselves.

Example

The example does not have nice visuals but it should give you an idea of what you can do with Fosite and a few lines of code.

Authorize Code Grant

You can run this minimalistic example by doing

go get github.com/ory-am/fosite/fosite-example
go install github.com/ory-am/fosite/fosite-example
fosite-example

There should be a server listening on localhost:3846. You can check out the example's source code here.

A word on quality

We tried to set up as many tests as possible and test for as many cases covered in the RFCs as possible. But we are only human. Please, feel free to add tests for the various cases defined in the OAuth2 RFCs 6749 and 6819 or any other cases that improve the tests.

Everyone writing an RFC conform test that breaks with the current implementation, will receive a place in the Hall of Fame!

A word on security

Please be aware that Fosite only secures parts your server side security. You still need to secure your apps and clients, keep your tokens safe, prevent CSRF attacks, ensure database security, use valid and strong TLS certificates and much more. If you need any help or advice feel free to contact our security staff through our website!

We have given the various specifications, especially OAuth 2.0 Threat Model and Security Considerations, a very close look and included everything we thought was in the scope of this framework. Here is a complete list of things we implemented in Fosite:

Not implemented yet:

Additionally, we added these safeguards:

  • Enforcing random states: Without a random-looking state the request will fail.
  • Advanced Token Validation: Tokens are layouted as <key>.<signature> where <signature> is created using HMAC-SHA256, a global secret and the client's secret. Read more about this workflow in the proposal. This is what a token can look like: /tgBeUhWlAT8tM8Bhmnx+Amf8rOYOUhrDi3pGzmjP7c=.BiV/Yhma+5moTP46anxMT6cWW8gz5R5vpC9RbpwSDdM=
  • Enforging scopes: By default, you always need to include the fosite scope or fosite will not execute the request properly. Obviously, you can change the scope to basic or core but be aware that you should use scopes if you use OAuth2.

Sections below Section 5 that are not covered in the list above should be reviewed by you. If you think that a specific section should be something that is covered in Fosite, feel free to create an issue.

It is strongly encouraged to use the handlers shipped with Fosite as they follow the specs and are well tested.

A word on extensibility

Fosite is extensible ... because OAuth2 is an extensible and flexible framework. Fosite let's you register custom token and authorize endpoint handlers with the security that the requests have been validated against the OAuth2 specs beforehand. You can easily extend Fosite's capabilities. For example, if you want to provide OpenID Connect on top of your OAuth2 stack, that's no problem. Or custom assertions, what ever you like and as long as it is secure. ;)

Usage

There is an API documentation available at godoc.org/ory-am/fosite.

Installation

You will need Go installed on your machine and it is required that you have set up your GOPATH environment variable. Fosite is being shipped through gopkg.in so new updates don't break your code. To see a full list of available versions check gopkg.in/ory-am/fosite.v0.

Right now, there is only an unstable release versioned as the v0 branch:

go get gopkg.in/ory-am/fosite.v0/...
package main

import(
    "github.com/ory-am/fosite"
    "github.com/ory-am/handler/core/explicit"
	"golang.org/x/net/context"
)

func fositeFactory() fosite.OAuth2Provider {
    // NewMyStorageImplementation should implement all storage interfaces.
    var store = newMyStorageImplementation()

    f := fosite.NewFosite(store)
    accessTokenLifespan := time.Hour

    // Let's enable the explicit authorize code grant!
    explicitHandler := &explicit.AuthorizeExplicitGrantTypeHandler struct {
        Enigma:           &enigma.HMACSHAEnigma{GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows")},
        Store:            store,
        AuthCodeLifespan: time.Minute * 10,
    }
    f.AuthorizeEndpointHandlers.Add("code", explicitHandler)
    f.TokenEndpointHandlers.Add("code", explicitHandler)

    // Next let's enable the implicit one!
    explicitHandler := &implicit.AuthorizeImplicitGrantTypeHandler struct {
        Enigma:              &enigma.HMACSHAEnigma{GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows")},
        Store:               store,
        AccessTokenLifespan: accessTokenLifespan,
    }
    f.AuthorizeEndpointHandlers.Add("implicit", implicitHandler)

    return f
}

// Let's assume that we're in a http handler
func handleAuth(rw http.ResponseWriter, r *http.Request) {
    ctx := fosite.NewContext()

    // Let's create an AuthorizeRequest object!
    // It will analyze the request and extract important information like scopes, response type and others.
    authorizeRequest, err := oauth2.NewAuthorizeRequest(ctx, r)
    if err != nil {
       oauth2.WriteAuthorizeError(rw, req, err)
       return
    }

    // you have now access to authorizeRequest, Code ResponseTypes, Scopes ...
    // and can show the user agent a login or consent page
    //
    // or, for example:
    // if authorizeRequest.GetScopes().Has("admin") {
    //     http.Error(rw, "you're not allowed to do that", http.StatusForbidden)
    //     return
    // }

    // it would also be possible to redirect the user to an identity provider (google, microsoft live, ...) here
    // and do fancy stuff like OpenID Connect amongst others

    // Once you have confirmed the users identity and consent that he indeed wants to give app XYZ authorization,
    // you will use the user's id to create an authorize session
    user := "12345"

    // mySessionData is going to be persisted alongside the other data. Note that mySessionData is arbitrary.
    // You will however absolutely need the user id later on, so at least store that!
    mySessionData := struct {
        User string
        UsingIdentityProvider string
        Foo string
    } {
        User: user,
        UsingIdentityProvider: "google",
        Foo: "bar",
    }

    // if you want to support OpenID Connect, this would be a good place to do stuff like
    // user := getUserFromCookie()
    // mySessionData := NewImplementsOpenIDSession()
    // if authorizeRequest.GetScopes().Has("openid") {
    //     if authorizeRequest.GetScopes().Has("email") {
    //         mySessionData.AddField("email", user.Email)
    //     }
    //     mySessionData.AddField("id", user.ID)
    // }
    //

    // Now is the time to handle the response types
    // You can use a custom list of response type handlers by setting
    // oauth2.AuthorizeEndpointHandlers = []fosite.AuthorizeEndpointHandler{}
    //
    // Each AuthorizeEndpointHandler is responsible for managing his own state data. For example, the code response type
    // handler stores the access token and the session data in a database backend and retrieves it later on
    // when handling a grant type.
    //
    // If you use advanced AuthorizeEndpointHandlers it is a good idea to read the README first and check if your
    // session object needs to implement any interface. Think of the session as a persistent context
    // for the handlers.
    response, err := oauth2.NewAuthorizeResponse(ctx, req, authorizeRequest, &mySessionData)
    if err != nil {
       oauth2.WriteAuthorizeError(rw, req, err)
       return
    }

    // The next step is going to redirect the user by either using implicit or explicit grant or both (for OpenID connect)
    oauth2.WriteAuthorizeResponse(rw, authorizeRequest, response)

    // Done! The client should now have a valid authorize code!
}

// ...

Exemplary Token Endpoint

// ...

func handleToken(rw http.ResponseWriter, req *http.Request) {
    ctx := NewContext()

    // First we need to define a session object. Some handlers might require the session to implement
    // a specific interface, so keep that in mind when using them.
    var mySessionData struct {
        User string
        UsingIdentityProvider string
        Foo string
    }

    // This will create an access request object and iterate through the registered TokenEndpointHandlers.
    // These might populate mySessionData so do not pass nils.
    accessRequest, err := oauth2.NewAccessRequest(ctx, req, &mySessionData)
    if err != nil {
       oauth2.WriteAccessError(rw, accessRequest, err)
       return
    }

    // Now we have access to mySessionData's populated values and can do crazy things.

    // Next we create a response for the access request. Again, we iterate through the TokenEndpointHandlers
    // and aggregate the result in response.
    response, err := oauth2.NewAccessResponse(ctx, req, accessRequest, &mySessionData)
    if err != nil {
       oauth2.WriteAccessError(rw, accessRequest, err)
       return
    }

    // All done, send the response.
    oauth2.WriteAccessResponse(rw, accessRequest, response)
}

Extensible handlers

You can replace the Token and Authorize endpoint logic by modifying Fosite.TokenEndpointHandlers and Fosite.AuthorizeEndpointHandlers.

Let's take the explicit authorize handler. He is responsible for handling the authorize code workflow.

If you want to enable the handler able to handle this workflow, you can do this:

handler := &explicit.AuthorizeExplicitGrantTypeHandler{
	Generator: &enigma.HMACSHAEnigma{GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows")},
	Store:     myCodeStore, // Needs to implement CodeResponseTypeStorage
}
oauth2 := &fosite.Fosite{
	AuthorizeEndpointHandlers: fosite.AuthorizeEndpointHandlers{
		handler,
	},
	TokenEndpointHandlers: fosite.TokenEndpointHandlers{
		handler,
	},
}

As you probably noticed, there are two types of handlers, one for the authorization /auth endpoint and one for the token /token endpoint. The AuthorizeExplicitEndpointHandler implements API requirements for both endpoints, while, for example, the AuthorizeImplicitEndpointHandler only implements the AuthorizeEndpointHandler API.

You can find a complete list of handlers inside the handler directory. A short list is documented here:

Replaceable storage

Fosite does not ship a storage implementation yet. To get fosite running, you need to implement github.com/ory-am/fosite.Storage. Additionally, most of the token / authorize endpoint handlers require a store as well. It is probably smart to implement all of those interfaces in one struct.

Develop fosite

You need git and golang installed on your system.

go get github.com/ory-am/fosite/... -d
cd $GOPATH/src/ github.com/ory-am/fosite
git status
git remote add myfork <url-to-your-fork>
go test ./...

Simple, right? Now you are ready to go! Make sure to run go test ./... often, detecting problems with your code rather sooner than later.

Useful commands

Create storage mocks

mockgen -destination internal/storage.go github.com/ory-am/fosite Storage
mockgen -destination internal/core_client_storage.go github.com/ory-am/fosite/handler/core/client ClientCredentialsGrantStorage
mockgen -destination internal/core_explicit_storage.go github.com/ory-am/fosite/handler/core/explicit AuthorizeCodeGrantStorage
mockgen -destination internal/core_implicit_storage.go github.com/ory-am/fosite/handler/core/implicit ImplicitGrantStorage
mockgen -destination internal/core_owner_storage.go github.com/ory-am/fosite/handler/core/owner ResourceOwnerPasswordCredentialsGrantStorage
mockgen -destination internal/core_refresh_storage.go github.com/ory-am/fosite/handler/core/refresh RefreshTokenGrantStorage

Create handler mocks

mockgen -destination internal/authorize_handler.go github.com/ory-am/fosite AuthorizeEndpointHandler
mockgen -destination internal/token_handler.go github.com/ory-am/fosite TokenEndpointHandler

Create stateful "context" mocks

mockgen -destination internal/client.go github.com/ory-am/fosite/client Client
mockgen -destination internal/access_request.go github.com/ory-am/fosite AccessRequester
mockgen -destination internal/access_response.go github.com/ory-am/fosite AccessResponder
mockgen -destination internal/authorize_request.go github.com/ory-am/fosite AuthorizeRequester
mockgen -destination internal/authorize_response.go github.com/ory-am/fosite AuthorizeResponder

Hall of Fame

This place is reserved for the fearless bug hunters, reviewers and contributors (alphabetical order).

Find out more about the author of Fosite and Hydra, and the Ory Company.

About

The security first OAuth2 framework for Google's Go Language in the era of micro services. Now in early access!

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 100.0%