Skip to content

Latest commit

 

History

History
416 lines (329 loc) · 19.3 KB

README.md

File metadata and controls

416 lines (329 loc) · 19.3 KB

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 and leaks your database.

If you are here to contribute, feel free to check this Pull Request.

Build Status Coverage Status

Fosite is in active development. Most of the framework is done and tested. We will use gopkg for releasing new versions of the API. 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.

These Standards have been reviewed during the development of Fosite:

Table of Contents

Motivation

Why write another OAuth2 server side library for Go Lang?

Other libraries are perfect for a non-critical set ups, but fail to comply with advanced security requirements. Additionally, the frameworks we analyzed did not support extension of the OAuth2 protocol easily. But OAuth2 is an extensible framework. Your OAuth2 should as well. This is unfortunately not an issue exclusive to Go's eco system but to many others as well.

Fosite was written because Hydra required a more secure and extensible OAuth2 library then the one it was using. We quickly realized, that OAuth2 implementations out there are not secure nor *extensible, so we decided to write one that is.

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.

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 your server side security. You still need to secure your apps and clients, keep your tokens safe, prevent CSRF attacks and much more. If you need any help or advice feel free to contact our security staff through our website!

We have given the 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 must use scopes if you use OAuth2.

It is strongly encouraged to use the handlers shipped with Fosite as the follow specs.

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.

The following list documents which sections of the RFCs we reviewed for each action. This list is not complete yet.

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

This section is WIP and we welcome discussions via PRs or in the issues.

Installation

Obviously, 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.

go get gopkg.in/ory-am/fosite.v{X}/...

To see a full list of available versions check the tags page. If you want to use api version 2 for example (version 2 does not exist yet), do:

go get gopkg.in/ory-am/fosite.v2/...

To use the unstable master branch, which is only recommended for testing purposes, do:

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

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

var oauth2 fosite.OAuth2Provider = fositeFactory()

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

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

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

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

// ...
// ...

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, req, 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, accessRequest, req, &mySessionData)
    if err != nil {
       oauth2.WriteAccessError(rw, req, 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.AuthorizeExplicitEndpointHandler{
	Generator: &enigma.HMACSHAEnigma{GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows")},
	Store:     myCodeStore, // Needs to implement CodeResponseTypeStorage
}
oauth2 := &fosite.Fosite{
	AuthorizeEndpointHandlers: []fosite.AuthorizeEndpointHandler{
		handler,
	},
	TokenEndpointHandlers: []fosite.TokenEndpointHandler{
		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:

  • github.com/ory-am/fosite/handler/authorize/explicit.AuthorizeExplicitEndpointHandler: implements the Authorization Code Grant
  • github.com/ory-am/fosite/handler/authorize/implicit.AuthorizeImplicitEndpointHandler: implements the Implicit Grant
  • github.com/ory-am/fosite/handler/authorize/token/owner.TokenROPasswordCredentialsEndpointHandler: implements the Resource Owner Password Credentials Grant
  • github.com/ory-am/fosite/handler/authorize/token/client.TokenClientCredentialsEndpointHandler: implements the Client Credentials Grant

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/authorize_storage.go github.com/ory-am/fosite/handler/authorize AuthorizeStorage
mockgen -destination internal/token_storage.go github.com/ory-am/fosite/handler/token TokenStorage

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/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.