openid

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2022 License: MIT, MIT Imports: 10 Imported by: 17

README

Go OpenId

godoc license

Summary

A Go package that implements web service middlewares for authenticating identities represented by OpenID Connect (OIDC) ID Tokens.

"OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It enables Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server" - OpenID Connect

Installation

go get github.com/emanoelxavier/openid2go/openid

Example

This example demonstrates how to use this package to validate incoming ID Tokens. It initializes the Configuration with the desired providers (OPs) and registers two middlewares: openid.Authenticate and openid.AuthenticateUser. The former performs the token validation while the latter, in addition to that, will forward the user information to the next handler.

import (
	"fmt"
	"net/http"

	"github.com/emanoelxavier/openid2go/openid"
)

func AuthenticatedHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "The user was authenticated!")
}

func AuthenticatedHandlerWithUser(u *openid.User, w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "The user was authenticated! The token was issued by %v and the user is %+v.", u.Issuer, u)
}

func Example() {
	configuration, err := openid.NewConfiguration(openid.ProvidersGetter(getProviders_googlePlayground))

	if err != nil {
		panic(err)
	}
	
	http.Handle("/user", openid.AuthenticateUser(configuration, openid.UserHandlerFunc(AuthenticatedHandlerWithUser)))
	http.Handle("/authn", openid.Authenticate(configuration, http.HandlerFunc(AuthenticatedHandler)))
	
	http.ListenAndServe(":5100", nil)
}

func myGetProviders() ([]openid.Provider, error) {
	provider, err := openid.NewProvider("https://providerissuer", []string{"myClientID"})

	if err != nil {
		return nil, err
	}

	return []openid.Provider{provider}, nil
}

This example is also available in the documentation of this package, for more details see GoDoc.

Tests

Unit Tests
go test github.com/emanoelxavier/openid2go/openid
Integration Tests

In addition to to unit tests, this package also comes with integration tests that will validate real ID Tokens issued by real OIDC providers. The following command will run those tests:

go test -tags integration github.com/emanoelxavier/openid2go/openid -issuer=[issuer] -clientID=[clientID] -idToken=[idToken]

Replace [issuer], [clientID] and [idToken] with the information from an identity provider of your choice.

For a quick spin you can use it with tokens issued by Google for the Google OAuth PlayGround entering "openid" (without quotes) within the scope field and copying the issued ID Token. For this provider and client the values will be:

go test -tags integration github.com/emanoelxavier/openid2go/openid -issuer=https://accounts.google.com -clientID=407408718192.apps.googleusercontent.com -idToken=copiedIDToken

Contributing

  1. Open an issue if found a bug or have a functional request.
  2. Disccuss.
  3. Branch off, write the fix with test(s) and commit attaching to the issue.
  4. Make a pull request.

Documentation

Overview

Package openid implements web service middlewares for authenticating identities represented by OpenID Connect (OIDC) ID Tokens. For details on OIDC go to http://openid.net/specs/openid-connect-core-1_0.html

The middlewares will: extract the ID Token from the request; retrieve the OIDC provider (OP) configuration and signing keys; validate the token and provide the user identity and claims to the underlying web service.

The Basics

At the core of this package are the Authenticate and AuthenticateUser middlewares. To use either one of them you will need an instance of the Configuration type, to create that you use NewConfiguration.

func Authenticate(conf *Configuration, h http.Handler) http.Handler
func AuthenticateUser(conf *Configuration, h UserHandler) http.Handler
NewConfiguration(options ...option) (*Configuration, error)

// options:

func ErrorHandler(eh ErrorHandlerFunc) func(*Configuration) error
func ProvidersGetter(pg GetProvidersFunc) func(*Configuration) error

// extension points:

type ErrorHandlerFunc func(error, http.ResponseWriter, *http.Request) bool
type GetProvidersFunc func() ([]Provider, error)

The Example below demonstrates these elements working together.

Token Parsing

Both Authenticate and AuthenticateUser middlewares expect the incoming requests to have an HTTP Authorization header with the content 'Bearer [idToken]' where [idToken] is a valid ID Token issued by an OP. For instance:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...

By default, requests that do not contain an Authorization header with this content will not be forwarded to the next HTTP handler in the pipeline, instead they will fail back to the client with HTTP status 400/Bad Request.

Token Validation

Once parsed the ID Token will be validated:

  1. Is the token a valid jwt?
  2. Is the token issued by a known OP?
  3. Is the token issued for a known client?
  4. Is the token valid at the time ('not use before' and 'expire at' claims)?
  5. Is the token signed accordingly?

The signature validation is done with the public keys retrieved from the jwks_uri published by the OP in its OIDC metadata (https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata).

The token's issuer and audiences will be verified using a collection of the type Provider. This collection is retrieved by calling the implementation of the function GetProvidersFunc registered with the Configuration. If the token issuer matches the Issuer of any of the providers and the token audience matches at least one of the ClientIDs of the respective provider then the token is considered valid.

func myGetProviders() ([]openid.Provider, error) {
    p, err := openid.NewProvider("https://accounts.google.com",
                                 []string{"407408718192.apps.googleusercontent.com"})
    // ....
    return []openid.Provider{p}, nil
}

c, _ := openid.NewConfiguration(openid.ProvidersGetter(myGetProviders))

In code above only tokens with Issuer claim ('iss') https://accounts.google.com and Audiences claim ('aud') containing "407408718192.apps.googleusercontent.com" can be valid.

By default, when the token validation fails for any reason the requests will not be forwarded to the next handler in the pipeline, instead they will fail back to the client with HTTP status 401/Unauthorized.

Error Handling

The default behavior of the Authenticate and AuthenticateUser middlewares upon error conditions is: the execution pipeline is stopped (the next handler will not be executed), the response will contain status 400 when a token is not found and 401 when it is invalid, and the response will also contain the error message. This behavior can be changed by implementing a function of type ErrorHandlerFunc and registering it using ErrorHandler with the Configuration.

type ErrorHandlerFunc func(error, http.ResponseWriter, *http.Request) bool
func ErrorHandler(eh ErrorHandlerFunc) func(*Configuration) error

For instance:

func myErrorHandler(e error, w http.ResponseWriter, r *http.Request) bool {
    fmt.Fprintf(w, e.Error())
    return false
}

c, _ := openid.NewConfiguration(openid.ProvidersGetter(myGetProviders),
                                openid.ErrorHandler(myErrorHandler))

In the code above myErrorHandler adds the error message to the response and let the execution continue to the next handler in the pipeline (returning false) for all error types. You can use this extension point to fine tune what happens when a specific error is returned by your implementation of the GetProvidersFunc or even for the error types and codes exported by this package:

type ValidationError struct
type ValidationErrorCode uint32
type SetupError struct
type SetupErrorCode uint32

Authenticate vs AuthenticateUser

Both middlewares Authenticate and AuthenticateUser behave exactly the same way when it comes to parsing and validating the ID Token. The only difference is that AuthenticateUser will forward the information about the user's identity from the ID Token to the next handler in the pipeline. If your service does not need to know the identity of the authenticated user then Authenticate will suffice, otherwise your choice is AuthenticateUser. In order to receive the User information from the AuthenticateUser the next handler in the pipeline must implement the interface UserHandler with the following function:

ServeHTTPWithUser(*User, http.ResponseWriter, *http.Request)

You can also make use of the function adapter UserHandlerFunc as shown in the example below:

func myHandlerWithUser(u *openid.User, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Authenticated! The user is %+v.", u)
}

http.Handle("/user", openid.AuthenticateUser(c, openid.UserHandlerFunc(myHandlerWithUser)))
Example

This example demonstrates how to use of the openid middlewares to validate incoming ID Tokens in the HTTP Authorization header with the format 'Bearer id_token'. It initializes the Configuration with the desired providers (OPs) and registers two middlewares: openid.Authenticate and openid.AuthenticateUser. The former will validate the ID Token and fail the call if the token is not valid. The latter will do the same but forward the user's information extracted from the token to the next handler.

package main

import (
	"fmt"
	"net/http"

	"github.com/TykTechnologies/openid2go/openid"
)

func AuthenticatedHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "The user was authenticated!")
}

func AuthenticatedHandlerWithUser(u *openid.User, w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "The user was authenticated! The token was issued by %v and the user is %+v.", u.Issuer, u)
}

func UnauthenticatedHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Reached without authentication!")
}

// This example demonstrates how to use of the openid middlewares to validate incoming
// ID Tokens in the HTTP Authorization header with the format 'Bearer id_token'.
// It initializes the Configuration with the desired providers (OPs) and registers two
// middlewares: openid.Authenticate and openid.AuthenticateUser.
// The former will validate the ID Token and fail the call if the token is not valid.
// The latter will do the same but forward the user's information extracted from the token to the next handler.
func main() {
	configuration, err := openid.NewConfiguration(openid.ProvidersGetter(getProviders_googlePlayground))

	if err != nil {
		panic(err)
	}

	http.Handle("/user", openid.AuthenticateUser(configuration, openid.UserHandlerFunc(AuthenticatedHandlerWithUser)))
	http.Handle("/authn", openid.Authenticate(configuration, http.HandlerFunc(AuthenticatedHandler)))
	http.HandleFunc("/unauth", UnauthenticatedHandler)

	http.ListenAndServe(":5100", nil)
}

// getProviders returns the identity providers that will authenticate the users of the underlying service.
// A Provider is composed by its unique issuer and the collection of client IDs registered with the provider that
// are allowed to call this service.
// On this example Google OP is the provider of choice and the client ID used corresponds
// to the Google OAUTH Playground https://developers.google.com/oauthplayground
func getProviders_googlePlayground() ([]openid.Provider, error) {
	provider, err := openid.NewProvider("https://accounts.google.com", []string{"407408718192.apps.googleusercontent.com"})

	if err != nil {
		return nil, err
	}

	return []openid.Provider{provider}, nil
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Authenticate

func Authenticate(conf *Configuration, h http.Handler) http.Handler

The Authenticate middleware performs the validation of the OIDC ID Token. If an error happens, i.e.: expired token, the next handler may or may not executed depending on the provided ErrorHandlerFunc option. The default behavior, determined by validationErrorToHTTPStatus, stops the execution and returns Unauthorized. If the validation is successful then the next handler(h) will be executed.

func AuthenticateUser

func AuthenticateUser(conf *Configuration, h UserHandler) http.Handler

The AuthenticateUser middleware performs the validation of the OIDC ID Token and forwards the authenticated user's information to the next handler in the pipeline. If an error happens, i.e.: expired token, the next handler may or may not executed depending on the provided ErrorHandlerFunc option. The default behavior, determined by validationErrorToHTTPStatus, stops the execution and returns Unauthorized. If the validation is successful then the next handler(h) will be executed and will receive the authenticated user information.

func CheckAndSplitHeader

func CheckAndSplitHeader(h string) (t string, err error)

func ErrorHandler

func ErrorHandler(eh ErrorHandlerFunc) func(*Configuration) error

The ErrorHandler option registers the function responsible for handling the errors returned during token validation. When this option is not used then the middleware will use the default internal implementation validationErrorToHTTPStatus.

func ProvidersGetter

func ProvidersGetter(pg GetProvidersFunc) func(*Configuration) error

The ProvidersGetter option registers the function responsible for returning the providers containing the valid issuer and client IDs used to validate the ID Token.

func TokenValidator

func TokenValidator(tv JWTTokenValidator) func(*Configuration) error

Types

type Configuration

type Configuration struct {
	IDTokenGetter GetIDTokenFunc
	// contains filtered or unexported fields
}

The Configuration contains the entities needed to perform ID token validation. This type should be instantiated at the application startup time.

func NewConfiguration

func NewConfiguration(options ...option) (*Configuration, error)

The NewConfiguration creates a new instance of Configuration and returns a pointer to it. This function receives a collection of the function type option. Each of those functions are responsible for setting some part of the returned *Configuration. If any if the option functions returns an error then NewConfiguration will return a nil configuration and that error.

type ErrorHandlerFunc

type ErrorHandlerFunc func(error, http.ResponseWriter, *http.Request) bool

The ErrorHandlerFunc represents the function used to handle errors during token validation. Applications can have their own implementation of this function and register it using the ErrorHandler option. Through this extension point applications can choose what to do upon different error types, for instance return an certain HTTP Status code and/or include some detailed message in the response. This function returns false if the next handler registered after the ID Token validation should be executed when an error is found or true if the execution should be stopped.

type GetIDTokenFunc

type GetIDTokenFunc func(r *http.Request) (token string, err error)

GetIdTokenFunc represents the function used to provide the OIDC idToken. It uses the provided request(r) to return the id token string(token). If the token was not found or had a bad format this function will return an error.

type GetProvidersFunc

type GetProvidersFunc func() ([]Provider, error)

The GetProvidersFunc defines the function type used to retrieve the collection of allowed OP(s) along with the respective client IDs registered with those providers that can access the backend service using this package. A function of this type must be provided to NewConfiguration through the option ProvidersGetter. The given function will then be invoked for every request intercepted by the Authenticate or AuthenticateUser middleware.

type JWTTokenValidator

type JWTTokenValidator interface {
	Validate(t string) (jt *jwt.Token, err error)
}

type Provider

type Provider struct {
	Issuer    string
	ClientIDs []string
}

Provider represents an OpenId Identity Provider (OP) and contains the information needed to perform validation of ID Token. See OpenId terminology http://openid.net/specs/openid-connect-core-1_0.html#Terminology.

The Issuer uniquely identifies an OP. This field will be used to validate the 'iss' claim present in the ID Token.

The CliendIDs contains the list of client IDs registered with the OP that are meant to be accepted by the service using this package. These values are used to validate the 'aud' clain present in the ID Token.

func NewProvider

func NewProvider(issuer string, clientIDs []string) (Provider, error)

NewProvider returns a new instance of a Provider created with the given issuer and clientIDs.

type SetupError

type SetupError struct {
	Err     error
	Code    SetupErrorCode
	Message string
}

SetupError represents the error returned by operations called during middleware setup.

func (SetupError) Error

func (se SetupError) Error() string

Error returns a formatted string containing the error Message.

type SetupErrorCode

type SetupErrorCode uint32

SetupErrorCode is the type of error code that can be returned by the operations done during middleware setup.

const (
	SetupErrorInvalidIssuer           SetupErrorCode = iota // Invalid issuer provided during setup.
	SetupErrorInvalidClientIDs                              // Invalid client id collection provided during setup.
	SetupErrorEmptyProviderCollection                       // Empty collection of providers provided during setup.
)

Setup error constants.

type User

type User struct {
	Issuer string
	ID     string
	Claims map[string]interface{}
}

User represents the authenticated user encapsulating information obtained from the validated ID token.

The Issuer contains the value from the 'iss' claim found in the ID Token.

The ID contains the value of the 'sub' claim found in the ID Token.

The Claims contains all the claims present found in the ID Token

func AuthenticateOIDWithUser

func AuthenticateOIDWithUser(c *Configuration, rw http.ResponseWriter, req *http.Request) (*User, *jwt.Token, bool)

Exported authenticate so we don't need to use the middleware

type UserHandler

type UserHandler interface {
	ServeHTTPWithUser(*User, http.ResponseWriter, *http.Request)
}

The UserHandler represents a handler to be registered by the middleware AuthenticateUser. This handler allows the AuthenticateUser middleware to forward information about the the authenticated user to the rest of the application service.

ServeHTTPWithUser is similar to the http.ServeHTTP function. It contains an additional paramater *User, which is used by the AuthenticateUser middleware to pass information about the authenticated user.

type UserHandlerFunc

type UserHandlerFunc func(*User, http.ResponseWriter, *http.Request)

The UserHandlerFunc is an adapter to allow the use of functions as UserHandler. This is similar to using http.HandlerFunc as http.Handler

func (UserHandlerFunc) ServeHTTPWithUser

func (f UserHandlerFunc) ServeHTTPWithUser(u *User, w http.ResponseWriter, r *http.Request)

ServeHttpWithUser calls f(u, w, r)

type ValidationError

type ValidationError struct {
	Err        error
	Code       ValidationErrorCode
	Message    string
	HTTPStatus int
}

ValidationError represents the error returned by operations called during token validation.

func (ValidationError) Error

func (ve ValidationError) Error() string

Error returns a formatted string containing the error Message.

type ValidationErrorCode

type ValidationErrorCode uint32

ValidationErrorCode is the type of error code that can be returned by the operations done during token validation.

const (
	ValidationErrorAuthorizationHeaderNotFound        ValidationErrorCode = iota // Authorization header not found on request.
	ValidationErrorAuthorizationHeaderWrongFormat                                // Authorization header unexpected format.
	ValidationErrorAuthorizationHeaderWrongSchemeName                            // Authorization header unexpected scheme.
	ValidationErrorJwtValidationFailure                                          // Jwt token validation failed with a known error.
	ValidationErrorJwtValidationUnknownFailure                                   // Jwt token validation failed with an unknown error.
	ValidationErrorInvalidAudienceType                                           // Unexpected token audience type.
	ValidationErrorInvalidAudience                                               // Unexpected token audience content.
	ValidationErrorAudienceNotFound                                              // Unexpected token audience value. Audience not registered.
	ValidationErrorInvalidIssuerType                                             // Unexpected token issuer type.
	ValidationErrorInvalidIssuer                                                 // Unexpected token issuer content.
	ValidationErrorIssuerNotFound                                                // Unexpected token value. Issuer not registered.
	ValidationErrorGetOpenIdConfigurationFailure                                 // Failure while retrieving the OIDC configuration.
	ValidationErrorDecodeOpenIdConfigurationFailure                              // Failure while decoding the OIDC configuration.
	ValidationErrorGetJwksFailure                                                // Failure while retrieving jwk set.
	ValidationErrorDecodeJwksFailure                                             // Failure while decoding the jwk set.
	ValidationErrorEmptyJwk                                                      // Empty jwk returned.
	ValidationErrorEmptyJwkKey                                                   // Empty jwk key set returned.
	ValidationErrorMarshallingKey                                                // Error while marshalling the signing key.
	ValidationErrorKidNotFound                                                   // Key identifier not found.
	ValidationErrorInvalidSubjectType                                            // Unexpected token subject type.
	ValidationErrorInvalidSubject                                                // Unexpected token subject content.
	ValidationErrorSubjectNotFound                                               // Token missing the 'sub' claim.
	ValidationErrorIdTokenEmpty                                                  // Empty ID token.
	ValidationErrorEmptyProviders                                                // Empty collection of providers.
)

Validation error constants.

Jump to

Keyboard shortcuts

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