oidcauth

package module
v0.0.0-...-587d70a Latest Latest
Warning

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

Go to latest
Published: Feb 5, 2024 License: MIT Imports: 25 Imported by: 0

README

oidc-auth

OIDC/OAuth2 Authentication Middleware.

Documentation

Index

Constants

View Source
const (
	MAX_COOKIE_SIZE = 4096
	// According to
	// https://stackoverflow.com/questions/3326210/can-http-headers-be-too-big-for-browsers
	// chrome does not handle more than 256k of total headers. Hence we do
	// not have to support more than 64 cookies (256k / 4k)
	MAX_COOKIE_COUNT = 64
)

Variables

View Source
var ErrNotSupported = errors.New("not supported")

ErrNotSupported is returned if an action on a provider is called which is not supported. For example if you call Revoke but the provider does not have configured a revocation endpoint.

Functions

func AuthenticateHandler

func AuthenticateHandler(sm *sessionManager, loginEndpoint string, next http.Handler) http.Handler

AuthenticateHandler loads the session and sets it in the context. If there is no session available it initiates a login be redirecting the request to the login endpoint. If a session is available but it is no longer valid and if the session contains a refresh_token it tries to obtain a new session using the refresh token.

func CallbackHandler

func CallbackHandler(sm *sessionManager, postCallbackHandler PostCallbackHandler, errorHandler ErrorHandler) http.Handler

func ContextWithSession

func ContextWithSession(parent context.Context, s *SessionContext) context.Context

func DebugHandler

func DebugHandler(sm *sessionManager, providers []*Provider) http.Handler

DebugHandler returns information about the session including the tokens.

func GetCookieValue

func GetCookieValue(r *http.Request, name string) (string, error)

func LoadSessionHandler

func LoadSessionHandler(sm *sessionManager, next http.Handler) http.Handler

LoadSessionHandler loads the session and makes it available in the context for subsequent handler.

func LoginHandler

func LoginHandler(sm *sessionManager, providerSelectionHandler http.Handler, errorHandler ErrorHandler) http.Handler

LoginHandler returns a handler which sets a state and then redirects the request to the authorization endpoint of the provider. If multiple providers are configured the provider is selected via the query paramter provider=<providerID>. If multiple providers are configured providerSelectionHandler can be used to render a provider selection dialog.

func LogoutHandler

func LogoutHandler(sm *sessionManager, postLogoutHandler http.Handler) http.Handler

LogoutHandler deletes the session cookies, revokes the token (if supportd by the provider) and redirects to the end_session_uri of the provider (if supported by the provider). If no end_session_uri is available the postLogoutHandler is run.

func NewDefaultSessionInfoHandler

func NewDefaultSessionInfoHandler(sm *sessionManager, tm *templateManager, pathSet PathSet, errorHandler ErrorHandler) http.Handler

func NewSessionManager

func NewSessionManager(hashKey, encryptionKey []byte, providerSet *providerSet, cookieOptions *CookieOptions) (*sessionManager, error)

NewSessionManager creates a new sessionManager which is responsible to set and get encrypted session cookies.

func NewTemplateManager

func NewTemplateManager(directory string, devMode bool) (*templateManager, error)

func ProviderSelectionHandler

func ProviderSelectionHandler(appName string, providers []*Provider, tm *templateManager) http.Handler

ProviderSelectionHandler returns a handler which shows a provider selection dialog.

func RefreshHandler

func RefreshHandler(sm *sessionManager, postRefreshHandler http.Handler, errorHandler ErrorHandler) http.Handler

func RemoveSessionHandler

func RemoveSessionHandler(sm *sessionManager) http.Handler

RemoveSessionHandler removes the session.

func SessionInfoHandler

func SessionInfoHandler(sm *sessionManager, renderSessionHandler func(w http.ResponseWriter, r *http.Request, s *SessionContext), errorHandler ErrorHandler) http.Handler

SessionInfoHandler gets the session from the request using sessionManager and then renders the session info using the renderSessionInfo function. If the request accepts the MIME type application/json it returns a JSON representation of the session.

func SetCookie

func SetCookie(w http.ResponseWriter, r *http.Request, c *http.Cookie)

SetCookie splits a cookie with a big value into multiple cookies with corresponding suffixes to the cookie name (e.g. <name>_0, <name>_1). Based on the request old cookies which are no longer required are deleted.

Types

type Authenticator

type Authenticator struct {
	Config          *Config
	TemplateManager *templateManager
	SessionManager  *sessionManager
	Providers       []*Provider
	ErrorHandler    ErrorHandler
}

Authenticator combines privdes handlers and middlewares for the authentication using OIDC/OAuth2

func NewAuthenticator

func NewAuthenticator(ctx context.Context, c *Config) (*Authenticator, error)

NewAuthenticator validates the configuration and creates a new authenticator.

func (*Authenticator) AuthenticateHandler

func (a *Authenticator) AuthenticateHandler(next http.Handler) http.Handler

AuthenticateHandler checks if there is an existing valid session (not expired).

func (*Authenticator) CallbackHandler

func (a *Authenticator) CallbackHandler() http.Handler

func (*Authenticator) FullMiddleware

func (a *Authenticator) FullMiddleware(next http.Handler) *http.ServeMux

FullMiddleware authenticates each request using the authenticator. With the default configuration it shadows the following pathes:

  • /auth/login
  • /auth/callback
  • /auth/info
  • /auth/refresh
  • /auth/logout

All other pathes are passed to the next handler.

func (*Authenticator) LoginHandler

func (a *Authenticator) LoginHandler() http.Handler

func (*Authenticator) LogoutHandler

func (a *Authenticator) LogoutHandler() http.Handler

func (*Authenticator) RefreshHandler

func (a *Authenticator) RefreshHandler() http.Handler

func (*Authenticator) SessionInfoHandler

func (a *Authenticator) SessionInfoHandler() http.Handler

type ClaimCheckFunc

type ClaimCheckFunc func(claims map[string]any) error

type Config

type Config struct {
	// OAuth2 / OIDC
	Providers []ProviderConfig

	// CallbackURL is the url under which the callback path is reachable
	CallbackURL string

	// PostLogoutRedirectURI is the URL where you get redirected after an
	// RP initiated logut
	PostLogoutRediretURI string

	// BasePath is the path under which the other pathes get mapped
	BasePath string

	// LoginPath is the path under which the login flow gets initiated.
	LoginPath string

	// CallbackPath handle the oauth2 callback. It defaults to the path of
	// the CallbackURL if not specified.
	CallbackPath string

	// SessionInfoPath
	SessionInfoPath string

	// RefreshPath performs an explicit refresh
	RefreshPath string

	// LogoutPath deletes cookie, revokes token and redirect to IDPs logout
	// URL if available
	LogoutPath string

	// The external pathes are the paths under which the endpoint is
	// reachable from externally. If not set this defaults to the internal
	// path (same field without External prefix). These variables are only
	// required if between the client and this component the path gets
	// rewritten.
	// For example if you have an entry proxy (ingress) which routes
	// requests from entry.com/myapp/auth/info to myapp.com/auth/info you
	// have to configure a ExternalBasePath of /myapp.
	ExternalBasePath        string
	ExternalLoginPath       string
	ExternalSessionInfoPath string
	ExternalRefreshPath     string
	ExternalLogoutPath      string

	// secure cookie
	HashKey       []byte
	EncryptionKey []byte
	CookieConfig  *CookieOptions

	// AppName is used in templates. It is shown on the provider selection
	// to indicate where you are login into.
	AppName string

	TemplateDir     string
	TemplateDevMode bool

	// GetRequestID is used in the default error handler if set. It returns
	// the request id along with the error message.
	GetRequestID func(r *http.Request) string
}

func NewDefaultConfig

func NewDefaultConfig() *Config

func (*Config) PrepareAndValidate

func (c *Config) PrepareAndValidate() error

Prepare sets derived defaults and validates configuration.

type CookieManager

type CookieManager struct {
	// contains filtered or unexported fields
}

func NewCookieHandler

func NewCookieHandler(hashKey, encryptKey []byte) *CookieManager

func NewCookieHandlerWithOptions

func NewCookieHandlerWithOptions(hashKey []byte, encryptKey []byte, options *CookieOptions) *CookieManager

func (*CookieManager) Delete

func (c *CookieManager) Delete(w http.ResponseWriter, r *http.Request, name string)

func (*CookieManager) Get

func (c *CookieManager) Get(r *http.Request, name string, dstValue any) (bool, error)

func (*CookieManager) Set

func (c *CookieManager) Set(w http.ResponseWriter, r *http.Request, name string, value any, opts ...func(*http.Cookie)) error

type CookieOptions

type CookieOptions struct {
	Path     string
	Secure   bool
	HttpOnly bool
	Domain   string
	SameSite http.SameSite
	Duration time.Duration
}

func NewDefaultCookieOptions

func NewDefaultCookieOptions() *CookieOptions

func (*CookieOptions) NewCookie

func (co *CookieOptions) NewCookie(name, value string) *http.Cookie

type Endpoints

type Endpoints struct {
	// AuthorizationEndpoint https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
	AuthorizationEndpoint string `json:"authorization_endpoint"`
	// TokenEndpoint https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
	TokenEndpoint string `json:"token_endpoint"`
	// UserinfoEndpoint https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
	UserinfoEndpoint string `json:"userinfo_endpoint"`

	// EndSessionEndpoint https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
	EndSessionEndpoint string `json:"end_session_endpoint"`

	// IntrospectionEndpoint (discovery has no specification)
	// See https://datatracker.ietf.org/doc/html/rfc7662
	IntrospectionEndpoint string `json:"introspection_endpoint"`

	// RevocationEndpoint (discovery has no specification)
	// See https://datatracker.ietf.org/doc/html/rfc7009
	RevocationEndpoint string `json:"revocation_endpoint"`
}

func (*Endpoints) Merge

func (e *Endpoints) Merge(e2 *Endpoints)

Merge merges e2 into e

type ErrorHandler

type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)

func DefaultErrorHandler

func DefaultErrorHandler(getRequestID func(r *http.Request) string, logger *slog.Logger) ErrorHandler

type LoginState

type LoginState struct {
	// ProviderID to identify the provider we want to login with.
	ProviderID string

	// State for the OAuth2 code flow
	State string

	// URI to return to during the callback.
	URI string
}

LoginState gets set during the login before we redirect a request to the IDPs authorization endpoint.

type PathSet

type PathSet struct {
	// Login is the path to the login handler
	Login string

	// Logout is the path to the logout handler
	Logout string

	// Refresh is the path to the refresh handler
	Refresh string
}

PathSet contains all pathes for the session info template.

type PostCallbackHandler

type PostCallbackHandler func(w http.ResponseWriter, r *http.Request, s *SessionContext)

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

func NewProvider

func NewProvider(ctx context.Context, config ProviderConfig) (*Provider, error)

func NewProviderSet

func NewProviderSet(ctx context.Context, providerConfigs []ProviderConfig, modifier func(pc *ProviderConfig)) ([]*Provider, error)

func (*Provider) AuthorizationEndpoint

func (p *Provider) AuthorizationEndpoint(_ context.Context, state string) (string, error)

AuthorizationEndpoint returns the authorization endpoint where redirect clients to initiate a login.

func (*Provider) Config

func (p *Provider) Config() ProviderConfig

func (*Provider) EndSessionEndpoint

func (p *Provider) EndSessionEndpoint(_ context.Context, session *Session) (string, error)

EndSessionEndpoint returns the logout URL for the RP initiated logout if the end_session_endpoint is configured or an empty string otherwise. https://openid.net/specs/openid-connect-rpinitiated-1_0.html

func (*Provider) Exchange

func (p *Provider) Exchange(ctx context.Context, code string) (*Session, error)

Exchange performs the Access Token Request using code. See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3. Based on the returned Access Token Response it returns a session (see SessionSetupFunc).

func (*Provider) ID

func (p *Provider) ID() string

ID returns an identifier of the provider. If not set in ProviderConfig it gets calculated based on:

  • IssuerURL
  • ClientID
  • AuthorizationEndpoint
  • TokenEndpoint

func (*Provider) Refresh

func (p *Provider) Refresh(ctx context.Context, session *Session) (*Session, error)

Refresh uses the refresh token of an existing session to obtain a new session. See https://datatracker.ietf.org/doc/html/rfc6749#section-6. If the session has no refresh token it returns an error.

func (*Provider) Revoke

func (p *Provider) Revoke(ctx context.Context, token string) error

Revoke revokes a token using the revocation endpoint. See https://www.rfc-editor.org/rfc/rfc7009.html#section-2.1 for details. Usually you want to revoke the refresh_token because the RFC states that `If the particular token is a refresh token and the authorization server supports the revocation of access tokens, then the authorization server SHOULD also invalidate all access tokens based on the same authorization grant`. Revoke does return ErrNotSupported if no revocation endpoint is configured.

func (*Provider) String

func (p *Provider) String() string

String returns a string representation of the provider. Do not rely on the format.

type ProviderConfig

type ProviderConfig struct {
	// ID (optional) for the provider. This is espacially used in multi provider
	// scenarios where the provider ID is stored in the session to figure
	// out for each session to which provider it belongs.
	// If no ID is provided it is generated based on the configuration.
	ID string `json:"id,omitempty"`

	// Name (optional) is a descriptive name for the provider. It is used in multi
	// provider scenarios during the login process on the provider
	// selection screen.
	Name string `json:"name"`

	// IssuerURL is the URL for the OpenID discovery. For providers which
	// do not support OpenID discovery you can configure
	// AuthorizationEndpoint and TokenEndpoint instead.
	IssuerURL string `json:"issuer_url"`

	// ClientID
	ClientID string `json:"client_id"`

	// ClientSecret (optional) if not set the provider acts as a public
	// client.
	ClientSecret string `json:"client_secret"`

	// Scopes (optional) to use for the authorization.
	Scopes []string `json:"scopes"`

	// AuthorizationParameter (optional) are additional query parameters
	// for the authorization endpoint (e.g. resource).
	AuthorizationParameter url.Values `json:"authorization_parameters"`

	// TokenParameters (optional) are additional query parameters for the
	// token endpoint.
	TokenParameters url.Values `json:"token_parameters"`

	// CallbackURL used as redirect_uri
	CallbackURL string `json:"callback_url"`

	// PostLogoutRedirectURI (optional) is used during logout for the call
	// to the EndSessionEndpoint.
	PostLogoutRedirectURI string `json:"post_logout_redirect_uri"`

	// SetupSessionFunc allows to customize the Session based on the tokens
	// returned in the TokenResponse. For example exctract values from the
	// id_tokens of fetch additional information from a third-party servie
	// using the access_token. If SetupSessionFunc is nil only the defaultSessionSetupFunc is used
	SetupSessionFunc SessionSetupFunc `json:"-"`

	// Endpoints are usually discovered using OpenID discovery. If set
	// manual you can configure endpoints which can't be discovered or
	// overwrite endpoints from the discovery.
	Endpoints
}

type ServerErr

type ServerErr struct {
	HTTPStatusCode int    // HTTP status code
	ResponseText   string // Response text to the user
	Err            error  // wrapped error
}

ServerErr is a error which can be passed to error handler. It allows to control what message is shown to the user.

func ErrDirect

func ErrDirect(httpCode int, err error) *ServerErr

ErrDirect creates a ServerErr which returns the actual error text to the user

func ErrMessage

func ErrMessage(httpCode int, message string, err error) *ServerErr

ErrMessage creates a ServerErr which uses message as response text for the user

func (*ServerErr) Error

func (s *ServerErr) Error() string

func (*ServerErr) Unwrap

func (s *ServerErr) Unwrap() error

type Session

type Session struct {
	ProviderID string `json:"provider_id"`

	// Expiry specifies the time when the session expires. This is set
	// during the session setup and is usually obtained from the field
	// expires_in from the Access Token Response (see
	// https://datatracker.ietf.org/doc/html/rfc6749#section-5.1).
	Expiry time.Time `json:"expiry"`

	// Tokens usually stores the issued tokens from which the session got
	// created. If you don't need the tokens you can remove them during the
	// session setup which helps to keep the cookie small.
	Tokens *Tokens `json:"tokens,omitempty"`

	// User represents the authenticated user. The user can be initialized
	// during the session setup. Usually for this values from the claims in
	// the id_token are used.
	User *User `json:"user,omitempty"`
}

Session represents a session which usually gets stored encrypted in a cookie. A session is initialized based on a set of tokens from a provider (TokenResponse) in a SetupSessionFunc.

func (*Session) AccessToken

func (s *Session) AccessToken() string

func (*Session) HasAccessToken

func (s *Session) HasAccessToken() bool

func (*Session) HasIDToken

func (s *Session) HasIDToken() bool

func (*Session) HasRefreshToken

func (s *Session) HasRefreshToken() bool

func (*Session) IDToken

func (s *Session) IDToken() string

func (*Session) RefreshToken

func (s *Session) RefreshToken() string

func (*Session) Valid

func (s *Session) Valid() bool

type SessionContext

type SessionContext struct {
	*Session

	// Provider which issued the session
	Provider *Provider
}

SessionContext represents a Session and a reference to the Provider which issued the session.

func SessionFromContext

func SessionFromContext(ctx context.Context) *SessionContext

func (*SessionContext) Valid

func (s *SessionContext) Valid() bool

type SessionInfoTemplateData

type SessionInfoTemplateData struct {
	Session  *Session
	Provider *Provider
	Path     PathSet
}

SessionInfoTemplateData are the parameters for the session info template.

type SessionSetupFunc

type SessionSetupFunc func(ctx context.Context, p *Provider, t *TokenResponse, s *Session) error

SessionSetupFunc is used to setup a new session. This happens always after a IDP issues a new set of tokens. This is either on the initial login where we get a set of tokens using an authorization code or if we obtain a new set of tokens using a refresh_token. It turns the returend tokens into a session. This allows to customize the session setup based on the tokens. For example obtaining additional information like groups from other sources (e.g. userinfo endpoint) or setting a custom expiration time. Be aware that on a refresh not every provider does return a new id_token. If you return an error no new session will be established. Consider returning ServerErr to control what HTTP status code is retuned and what error is shown to the user.

func ChainSessionSetupFunc

func ChainSessionSetupFunc(sessionSetupFuncs ...SessionSetupFunc) SessionSetupFunc

ChainSessionSetupFunc chains multiple sessionSetupFuncs. If one function returns an error subsequent functions are not called.

func NewSessionClaimCheckFunc

func NewSessionClaimCheckFunc(claimCheckFunc ClaimCheckFunc) SessionSetupFunc

func RequireIDTokenGroup

func RequireIDTokenGroup(groups ...string) SessionSetupFunc

RequireIDTokenGroup verifies that at least one of the given groups is available in the id_token.

func SaveGroups

func SaveGroups() SessionSetupFunc

SaveGroups adds groups from id_token claims to session. It ignores errors.

type TokenResponse

type TokenResponse struct {
	// Token contains the standard OAuth2 tokens like the access_token.
	oauth2.Token

	// RawIDToken contains the id_token if it was available in the
	// response.
	RawIDToken string

	// IDToken contains the parsed and validated id_token if it was
	// available in the response.
	IDToken *oidc.IDToken
}

type Tokens

type Tokens struct {
	oauth2.Token
	IDToken string `json:"id_token"`
}

type User

type User struct {
	ID     string   `json:"id"`
	Name   string   `json:"name"`
	Groups []string `json:"groups,omitempty"`
	Extra  any      `json:"extra,omitempty"`
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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