oauth2

package module
v0.0.0-...-4debe6f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2015 License: MPL-2.0 Imports: 9 Imported by: 0

README

OAuth2 provider library for Go

GoDoc Build Status

Implements OAuth2 HTTP dancing in a somewhat strict manner. For instance:

  • 3rd party client apps are required to always report the scopes they are trying to gain access to when redirecting the resource owner to the web authorization form.
  • Always sends a Strict-Transport-Security header by default. You can disable it by passing a STS max-age of 0.
  • X-Frame-Options header is always sent along the authorization form
  • X-XSS-Protection is always sent.
  • Requires 3rd-party client apps to send the state request parameter in order to minimize risk of CSRF attacks.
  • Checks redirect URIs against pre-registered client URIs
  • Requires redirect URIs to use HTTPS scheme.
  • Does not allow clients to use dynamic redirect URIs.
  • Forces refresh-token rotation upon access-token refresh.
OAuth2 flows supported
  • Authorization Code
  • Implicit
  • Resource Owner Password Credentials
  • Client Credentials
Non goals

It is not a goal of this library to support:

  • Authentication
  • Session management
  • Backend storage, instead we defined an interface for users to implement and plug any backend storage of their preference.

How to use

This library was designed as a regular Go's HTTP handler. A brief example about how to use it:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/hooklift/oauth2"
	"github.com/hooklift/oauth2/providers/test"
)

func main() {
	// Authorization form
	authzForm := `
		<html>
		<body>
		{{if .Errors}}
			<div id="errors">
				<ul>
				{{range .Errors}}
					<li>{{.Code}}: {{.Desc}}</li>
				{{end}}
				</ul>
			</div>
		{{else}}
			<div id="client">
				<h2>{{.Client.Name}}</h2>
				<h3>{{.Client.Desc}}</h3>
				<a href="{{.Client.HomepageURL}}">
					<figure><img src="{{.Client.ProfileImgURL}}"/></figure>
				</a>
			</div>
			<div id="scopes">
				<ul>
					{{range .Scopes}}
						<li>{{.ID}}: {{.Desc}}</li>
					{{end}}
				</ul>
			</div>
			<form>
			 <input type="hidden" name="client_id" value="{{.Client.ID}}"/>
			 <input type="hidden" name="response_type" value="{{.GrantType}}"/>
			 <input type="hidden" name="redirect_uri" value="{{.Client.RedirectURL}}"/>
			 <input type="hidden" name="scope" value="{{StringifyScopes .Scopes}}"/>
			 <input type="hidden" name="state" value="{{.State}}"/>
			</form>
		{{end}}
		</body>
		</html>
	`

	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
		w.Write([]byte("Hellow World!"))
	})

	// A provider that implements oauth2.Provider interface
	provider := test.NewProvider(true)

	// Authorization handler to protect resources in this server
	authzHandler := oauth2.AuthzHandler(mux, provider)

	// OAuth2 handler to handle authorization and token requests
	oauth2Handlers := oauth2.Handler(authzHandler,
		oauth2.SetProvider(provider),
		oauth2.SetAuthzForm(authzForm),
		oauth2.SetAuthzEndpoint("/oauth2/authorize"),
		oauth2.SetTokenEndpoint("/oauth2/tokens"),
		// Strict Transport Security max age configuration
		oauth2.SetSTSMaxAge(time.Duration(8760)*time.Hour), // 1yr
		oauth2.SetAuthzExpiration(time.Duration(1)*time.Minute),
		oauth2.SetTokenExpiration(time.Duration(10)*time.Minute),
		oauth2.SetLoginURL("https://api.hooklift.io/accounts/login", "redirect_to"),
	)

	log.Fatal(http.ListenAndServe(":3000", oauth2Handlers))
}

Lastly, don't forget to implement the Provider interface.

Implemented specs

Also implements some considerations from: https://tools.ietf.org/html/rfc6819

Documentation

Overview

Package oauth2 implements the OAuth2 HTTP dancing in accordance with http://tools.ietf.org/html/rfc6749 and leaves the rest of the implementation to its users by requiring them to implement oauth2.Provider interface.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrRedirectURLMismatch = types.AuthzError{
		Code:        "access_denied",
		Description: "3rd-party client app provided a redirect_uri that does not match the URI registered for this client in our database.",
	}

	ErrRedirectURLInvalid = types.AuthzError{
		Code:        "access_denied",
		Description: "3rd-party client app provided an invalid redirect_uri. It does not comply with http://tools.ietf.org/html/rfc3986#section-4.3 or does not use HTTPS.",
	}

	ErrClientIDMissing = types.AuthzError{
		Code:        "unauthorized_client",
		Description: "3rd-party client app didn't send us its client ID.",
	}

	ErrClientIDNotFound = types.AuthzError{
		Code:        "unauthorized_client",
		Description: "3rd-party client app requesting access to your resources was not found in our database.",
	}

	ErrUnauthorizedClient = types.AuthzError{
		Code:        "unauthorized_client",
		Description: "You must provide an authorization header with your client credentials.",
	}

	ErrUnsupportedGrantType = types.AuthzError{
		Code:        "unsupported_grant_type",
		Description: "grant_type provided is not supported by this authorization server.",
	}

	ErrInvalidGrant = types.AuthzError{
		Code:        "invalid_grant",
		Description: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.",
	}

	ErrUnathorizedUser = types.AuthzError{
		Code:        "access_denied",
		Description: "Resource owner credentials are invalid.",
	}

	ErrInvalidScope = types.AuthzError{
		Code:        "invalid_scope",
		Description: "Scope exceeds the scope granted by the resource owner.",
	}

	ErrClientIDMismatch = types.AuthzError{
		Code:        "invalid_request",
		Description: "Authenticated client did not generate token used.",
	}

	ErrUnsupportedTokenType = types.AuthzError{
		Code:        "invalid_token",
		Description: "Unsupported token type.",
	}

	ErrAccessTokenRequired = types.AuthzError{
		Code:        "invalid_request",
		Description: "An access token is required to access this resource.",
	}

	ErrInvalidToken = types.AuthzError{
		Code:        "invalid_token",
		Description: "Access token expired or was revoked.",
	}

	ErrInsufficientScope = types.AuthzError{
		Code:        "insufficient_scope",
		Description: "The request requires higher privileges than provided by the access token.",
	}
)

Errors returned to resource owner in accordance with spec.

View Source
var AuthzHandlers map[string]func(http.ResponseWriter, *http.Request, config) = map[string]func(http.ResponseWriter, *http.Request, config){
	"GET":  CreateGrant,
	"POST": CreateGrant,
}

Handlers is a map to functions where each function handles a particular HTTP verb or method.

View Source
var TokenHandlers map[string]func(http.ResponseWriter, *http.Request, config) = map[string]func(http.ResponseWriter, *http.Request, config){
	"POST":   IssueToken,
	"DELETE": RevokeToken,
}

TokenHandlers is a map to functions where each function handles a particular HTTP verb or method.

Functions

func AuthzHandler

func AuthzHandler(next http.Handler, provider Provider) http.Handler

AuthzHandler is intended to be used at the resource server side to protect and validate access to its resources. In accordance with http://tools.ietf.org/html/rfc6749#section-7 and http://tools.ietf.org/html/rfc6750

func CreateGrant

func CreateGrant(w http.ResponseWriter, req *http.Request, cfg config)

CreateGrant generates the authorization code for 3rd-party clients to use in order to get access and refresh tokens, asking the resource owner for authorization.

func EncodeErrInURI

func EncodeErrInURI(u *url.URL, err types.AuthzError)

Encodes errors as query string values in accordance to http://tools.ietf.org/html/rfc6749#section-4.1.2.1

func ErrScopeRequired

func ErrScopeRequired(state string) types.AuthzError

func ErrServerError

func ErrServerError(state string, err error) types.AuthzError

func ErrStateRequired

func ErrStateRequired(state string) types.AuthzError

func ErrUnsupportedResponseType

func ErrUnsupportedResponseType(state string) types.AuthzError

Errors returned to 3rd-party client apps in accordance to spec.

func Handler

func Handler(next http.Handler, opts ...option) http.Handler

Handler handles OAuth2 requests for getting authorization grants as well as access and refresh tokens.

func IssueToken

func IssueToken(w http.ResponseWriter, req *http.Request, cfg config)

IssueToken handles all requests going to tokens endpoint.

func RevokeToken

func RevokeToken(w http.ResponseWriter, req *http.Request, cfg config)

Implements https://tools.ietf.org/html/rfc7009 It does not take into account token_type_hint as the common use case is to have access and refresh tokens uniquely identified throughout the system. That said, unsupported_token_type error responses are not produced by this implementation either.

func SetAuthzEndpoint

func SetAuthzEndpoint(endpoint string) option

AuthzEndpoint allows setting authorization endpoint. Defaults to "/oauth2/authzs"

The authorization endpoint is used to interact with the resource owner and obtain an authorization grant.

Since requests to the authorization endpoint result in user authentication and the transmission of clear-text credentials (in the HTTP response), the authorization server MUST require the use of TLS as described in Section 1.6 when sending requests to the authorization endpoint.

-- http://tools.ietf.org/html/rfc6749#section-3.1.1

func SetAuthzExpiration

func SetAuthzExpiration(e time.Duration) option

SetAuthzExpiration allows setting expiration time for authorization grant codes.

func SetAuthzForm

func SetAuthzForm(form string) option

SetAuthzForm sets authorization form to show to the resource owner.

func SetLoginURL

func SetLoginURL(u, redirectParam string) option

SetLoginURL allows to set a login URL to redirect users to when they don't have valid sessions. The authentication system should send back the user to the referer URL in order to complete the OAuth2 authorization process.

func SetProvider

func SetProvider(p Provider) option

SetProvider sets backend provider

func SetSTSMaxAge

func SetSTSMaxAge(maxAge time.Duration) option

SetSTSMaxAge sets Strict Transport Security maximum age. Defaults to 1yr.

func SetTokenEndpoint

func SetTokenEndpoint(endpoint string) option

TokenEndpoint allows setting token endpoint. Defaults to "/oauth2/tokens".

The token endpoint is used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).

Since requests to the token endpoint result in the transmission of clear-text credentials (in the HTTP request and response), the authorization server MUST require the use of TLS as described in Section 1.6 when sending requests to the token endpoint.

-- http://tools.ietf.org/html/rfc6749#section-3.2

func SetTokenExpiration

func SetTokenExpiration(e time.Duration) option

SetTokenExpiration allows setting expiration time for access tokens.

Types

type AuthzData

type AuthzData struct {
	// Client information.
	Client types.Client
	// Requested scope access from 3rd-party client
	Scopes types.Scopes
	// List of errors to display to the resource owner.
	Errors []types.AuthzError
	// Grant type is either "code" or "token" for implicit authorizations.
	GrantType string
	// State can be used to store CSRF tokens by the 3rd-party client app
	State string
}

AuthzData defines properties used to render the authorization form view that asks for authorization to the resource owner when using the web flow.

type Provider

type Provider interface {
	// AuthenticateClient authenticates a previously registered client.
	AuthenticateClient(username, password string) (types.Client, error)

	// AuthenticateUser authenticates resource owner.
	AuthenticateUser(username, password string) (valid bool)

	// ClientInfo returns 3rd-party client information
	ClientInfo(clientID string) (info types.Client, err error)

	// GrantInfo returns information about the authorization grant code.
	GrantInfo(code string) (types.Grant, error)

	// TokenInfo returns information about one specific token.
	TokenInfo(token string) (types.Token, error)

	// ScopesInfo parses the list of scopes requested by the client and
	// returns its descriptions for the resource owner to fully understand
	// what she is authorizing the client to access to. An error is returned
	// if the scopes list does not comply with http://tools.ietf.org/html/rfc6749#section-3.3
	//
	// Unrecognized or non-existent scopes are ignored.
	ScopesInfo(scopes string) (types.Scopes, error)

	// ResourceScopes returns the scopes associated with a given resource
	ResourceScopes(url *url.URL) (types.Scopes, error)

	// GenGrant issues and stores an authorization grant code, in a persistent storage.
	// The authorization code MUST expire shortly after it is issued to mitigate
	// the risk of leaks.  A maximum authorization code lifetime of 10 minutes is
	// RECOMMENDED. If an authorization code is used more than once, the authorization
	// server MUST deny the request and SHOULD revoke (when possible) all tokens
	// previously issued based on that authorization code.  The authorization
	// code is bound to the client identifier and redirection URI.
	// -- http://tools.ietf.org/html/rfc6749#section-4.1.2
	GenGrant(client types.Client, scopes types.Scopes, expiration time.Duration) (code types.Grant, err error)

	// GenToken generates and stores access and refresh tokens with the given
	// client information and authorization scope.
	GenToken(grant types.Grant, client types.Client, refreshToken bool, expiration time.Duration) (token types.Token, err error)

	// RevokeToken expires a specific token.
	RevokeToken(token string) error

	// RefreshToken refreshes an access token.
	RefreshToken(refreshToken types.Token, scopes types.Scopes) (accessToken types.Token, err error)

	// IsUserAuthenticated checks whether or not the resource owner has a valid session
	// with the system. If not, it redirects the user to the login URL.
	IsUserAuthenticated() bool
}

Provider defines functions required by the oauth2 package to properly work. Users of this package are required to implement them.

Directories

Path Synopsis
internal
providers
test
This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
Package types defines oauth2 reusable types.
Package types defines oauth2 reusable types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL