tokens

package
v0.4.7 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2024 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package tokens creates tokenmanager, responsible for signing, issuing, and validating tokens

Index

Constants

View Source
const (
	// ValidationErrorMalformed is returned when the token is malformed
	ValidationErrorMalformed uint32 = 1 << iota

	// ValidationErrorUnverifiableToken is returned when the token could not be verified because of signing problems
	ValidationErrorUnverifiable

	// ValidationErrorSignatureInvalid is returned when the signature is invalid
	ValidationErrorSignatureInvalid

	// ValidationErrorAudience is returned when AUD validation failed
	ValidationErrorAudience

	// ValidationErrorExpired is returned when EXP validation failed
	ValidationErrorExpired

	// ValidationErrorIssuedAt is returned when IAT validation failed
	ValidationErrorIssuedAt

	// ValidationErrorIssuer is returned when ISS validation failed
	ValidationErrorIssuer

	// ValidationErrorNotValidYet is returned when NBF validation failed
	ValidationErrorNotValidYet

	// ValidationErrorID is returned when JTI validation failed
	ValidationErrorID

	// ValidationErrorClaimsInvalid is returned when there is a generic claims validation failure
	ValidationErrorClaimsInvalid
)

The errors that might occur when parsing and validating a token

View Source
const DefaultRefreshAudience = "https://auth.datum.net/v1/refresh"

Variables

View Source
var (
	// ErrTokenManagerFailedInit returns when the token manager was not correctly provided signing keys
	ErrTokenManagerFailedInit = errors.New("token manager not initialized with signing keys")

	// ErrFailedRetrieveClaimsFromToken returns when claims can not be retrieved from an access token
	ErrFailedRetrieveClaimsFromToken = errors.New("could not retrieve claims from access token")

	// ErrTokenMissingKid returns when the kid cannot be found in the header of the token
	ErrTokenMissingKid = errors.New("token does not have kid in header")

	// ErrFailedParsingKid returns when the kid could not be parsed
	ErrFailedParsingKid = errors.New("could not parse kid: %s")

	// ErrUnknownSigningKey returns when the signing key fetched does not match the loaded managed keys
	ErrUnknownSigningKey = errors.New("unknown signing key")
)

Error constants

View Source
var (

	// ErrTokenMalformed returns when a token is malformed
	ErrTokenMalformed = errors.New("token is malformed")

	// ErrTokenUnverifiable is returned when the token could not be verified because of signing problems
	ErrTokenUnverifiable = errors.New("token is unverifiable")

	// ErrTokenSignatureInvalid  is returned when the signature is invalid
	ErrTokenSignatureInvalid = errors.New("token signature is invalid")

	// ErrTokenInvalidAudience is returned when AUD validation failed
	ErrTokenInvalidAudience = errors.New("token has invalid audience")

	// ErrTokenExpired  is returned when EXP validation failed
	ErrTokenExpired = errors.New("token is expired")

	// ErrTokenUsedBeforeIssued is returned when the token is used before issued
	ErrTokenUsedBeforeIssued = errors.New("token used before issued")

	// ErrTokenInvalidIssuer is returned when ISS validation failed
	ErrTokenInvalidIssuer = errors.New("token has invalid issuer")

	// ErrTokenNotValidYet is returned when NBF validation failed
	ErrTokenNotValidYet = errors.New("token is not valid yet")

	// ErrTokenNotValid is returned when the token is invalid
	ErrTokenNotValid = errors.New("token is invalid")

	// ErrTokenInvalidID is returned when the token has an invalid id
	ErrTokenInvalidID = errors.New("token has invalid id")

	// ErrTokenInvalidClaims is returned when the token has invalid claims
	ErrTokenInvalidClaims = errors.New("token has invalid claims")

	// ErrMissingEmail is returned when the token is attempted to be verified but the email is missing
	ErrMissingEmail = errors.New("unable to create verification token, email is missing")

	// ErrTokenMissingEmail is returned when the verification is missing an email address
	ErrTokenMissingEmail = errors.New("email verification token is missing email address")

	// ErrInvalidSecret is returned when the verification contains of secret of invalid length
	ErrInvalidSecret = errors.New("email verification token contains an invalid secret")

	// ErrMissingUserID is returned when a reset token is trying to be created but no user id is provided
	ErrMissingUserID = errors.New("unable to create reset token, user id is required")

	// ErrTokenMissingUserID is returned when the reset token is missing the required user id
	ErrTokenMissingUserID = errors.New("reset token is missing user id")

	// ErrInviteTokenMissingOrgID is returned when the invite token is missing the org owner ID match
	ErrInviteTokenMissingOrgID = errors.New("invite token is missing org id")

	// ErrInvitetokenMissingEmail
	ErrInvitetokenMissingEmail = errors.New("invite token is missing email")

	// ErrExpirationIsRequired is returned when signing info is provided a zero-value expiration
	ErrExpirationIsRequired = errors.New("signing info requires a non-zero expiration")

	// ErrFailedSigning is returned when an error occurs when trying to generate signing info with expiration
	ErrFailedSigning = errors.New("error occurred when attempting to signing info")

	// ErrTokenInvalid is returned when unable to verify the token with the signature and secret provided
	ErrTokenInvalid = errors.New("unable to verify token")
)
View Source
var TimeFunc = time.Now

Functions

func ExpiresAt

func ExpiresAt(tks string) (_ time.Time, err error)

ExpiresAt parses a JWT token and returns the expiration time if it exists

func GetAlgorithms

func GetAlgorithms() (algs []string)

GetAlgorithms returns a list of registered "alg" names

func IsExpired

func IsExpired(tks string) (bool, error)

IsExpired attempts to check if the provided token is expired

func NotBefore

func NotBefore(tks string) (_ time.Time, err error)

NotBefore parses a JWT token and returns the "NotBefore" time claim if it exists

func ParseUnverified

func ParseUnverified(tks string) (claims *jwt.RegisteredClaims, err error)

ParseUnverified parses a string of tokens and returns the claims and any error encountered

func RegisterSigningMethod

func RegisterSigningMethod(alg string, f func() SigningMethod)

RegisterSigningMethod registers the "alg" name and a factory function for signing method

Types

type CachedJWKSValidator

type CachedJWKSValidator struct {
	JWKSValidator
	// contains filtered or unexported fields
}

CachedJWKSValidator struct is a type that extends the functionality of the `JWKSValidator` struct. It adds caching capabilities to the JWKS validation process. It includes a `cache` field of type `*jwk.Cache` to store and retrieve the JWKS, an `endpoint` field to specify the endpoint from which to fetch the JWKS, and embeds the `JWKSValidator` struct to inherit its methods and fields. The `CachedJWKSValidator` struct also includes additional methods `Refresh` and`keyFunc` to handle the caching logic

func NewCachedJWKSValidator

func NewCachedJWKSValidator(ctx context.Context, cache *jwk.Cache, endpoint, audience, issuer string) (validator *CachedJWKSValidator, err error)

NewCachedJWKSValidator function is a constructor for creating a new instance of the `CachedJWKSValidator` struct. It takes in a `context.Context`, a `*jwk.Cache`, an endpoint string, an audience string, and an issuer string

func (*CachedJWKSValidator) Parse

func (v *CachedJWKSValidator) Parse(tks string) (claims *Claims, err error)

Parse an access or refresh token verifying its signature but without verifying its claims. This ensures that valid JWT tokens are still accepted but claims can be handled on a case-by-case basis; for example by validating an expired access token during reauthentication

func (*CachedJWKSValidator) Refresh

func (v *CachedJWKSValidator) Refresh(ctx context.Context) (err error)

Refresh method in the `CachedJWKSValidator` struct is responsible for refreshing the JWKS (JSON Web Key Set) cache. It takes in a `context.Context` as a parameter and returns an error if the refresh process fails

func (*CachedJWKSValidator) Verify

func (v *CachedJWKSValidator) Verify(tks string) (claims *Claims, err error)

Verify an access or a refresh token after parsing and return its claims.

type Claims

type Claims struct {
	jwt.RegisteredClaims
	// UserID is the internal generated ID for the user
	UserID string `json:"user_id,omitempty"`
	// Email associated with the user
	Email string `json:"email,omitempty"`
	// OrgID the JWT token is valid for
	OrgID string `json:"org,omitempty"`
	// ParentOrgID of the parent organization, if a child
	ParentOrgID string `json:"parentorg,omitempty"`
	// Tier the token is valid for
	Tier string `json:"tier,omitempty"`
	// DisplayName of the user
	DisplayName string `json:"displayName,omitempty"`
	// AvatarURL of the user
	AvatarURL string `json:"avatarURL,omitempty"`
}

Claims implements custom claims and extends the `jwt.RegisteredClaims` struct; we will store user-related elements here (and thus in the JWT Token) for reference / validation

func ParseUnverifiedTokenClaims

func ParseUnverifiedTokenClaims(tks string) (claims *Claims, err error)

ParseUnverifiedTokenClaims parses token claims from an access token

func (Claims) ParseEmail

func (c Claims) ParseEmail() ulid.ULID

ParseEmail is used to parse and return the email from the `Email` field of the claims.

func (Claims) ParseOrgID

func (c Claims) ParseOrgID() ulid.ULID

ParseOrgID parses and return the organization ID from the `OrgID` field of the claims

func (Claims) ParseParentOrgID

func (c Claims) ParseParentOrgID() ulid.ULID

ParseParentOrgID parses and returns the parent organization ID from the ParentOrgID field of the claims

func (Claims) ParseUserID

func (c Claims) ParseUserID() ulid.ULID

ParseUserID returns the ID of the user from the Subject of the claims

func (*Claims) VerifyAudience

func (c *Claims) VerifyAudience(cmp string, req bool) bool

func (*Claims) VerifyIssuer

func (c *Claims) VerifyIssuer(cmp string, req bool) bool

type Config

type Config struct {
	// KID represents the Key ID used in the configuration.
	KID string `json:"kid" koanf:"kid" jsonschema:"required"`
	// Audience represents the target audience for the tokens.
	Audience string `json:"audience" koanf:"audience" jsonschema:"required" default:"https://datum.net"`
	// RefreshAudience represents the audience for refreshing tokens.
	RefreshAudience string `json:"refreshAudience" koanf:"refreshAudience"`
	// Issuer represents the issuer of the tokens
	Issuer string `json:"issuer" koanf:"issuer" jsonschema:"required" default:"https://auth.datum.net" `
	// AccessDuration represents the duration of the access token is valid for
	AccessDuration time.Duration `json:"accessDuration" koanf:"accessDuration" default:"1h"`
	// RefreshDuration represents the duration of the refresh token is valid for
	RefreshDuration time.Duration `json:"refreshDuration" koanf:"refreshDuration" default:"2h"`
	// RefreshOverlap represents the overlap time for a refresh and access token
	RefreshOverlap time.Duration `json:"refreshOverlap" koanf:"refreshOverlap" default:"-15m" `
	// JWKSEndpoint represents the endpoint for the JSON Web Key Set
	JWKSEndpoint string `json:"jwksEndpoint" koanf:"jwksEndpoint" default:"https://api.datum.net/.well-known/jwks.json"`
	// Keys represents the key pairs used for signing the tokens
	Keys map[string]string `json:"keys" koanf:"keys" jsonschema:"required"`
}

Config defines the configuration settings for authentication tokens used in the server

type DiscoveryJSON

type DiscoveryJSON struct {
	Issuer                        string   `json:"issuer"`
	AuthorizationEP               string   `json:"authorization_endpoint"`
	TokenEP                       string   `json:"token_endpoint"`
	DeviceAuthorizationEP         string   `json:"device_authorization_endpoint"`
	UserInfoEP                    string   `json:"userinfo_endpoint"`
	MFAChallengeEP                string   `json:"mfa_challenge_endpoint"`
	JWKSURI                       string   `json:"jwks_uri"`
	RegistrationEP                string   `json:"registration_endpoint"`
	RevocationEP                  string   `json:"revocation_endpoint"`
	ScopesSupported               []string `json:"scopes_supported"`
	ResponseTypesSupported        []string `json:"response_types_supported"`
	CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
	ResponseModesSupported        []string `json:"response_modes_supported"`
	SubjectTypesSupported         []string `json:"subject_types_supported"`
	IDTokenSigningAlgValues       []string `json:"id_token_signing_alg_values_supported"`
	TokenEndpointAuthMethods      []string `json:"token_endpoint_auth_methods_supported"`
	ClaimsSupported               []string `json:"claims_supported"`
	RequestURIParameterSupported  bool     `json:"request_uri_parameter_supported"`
}

DiscoveryJSON is the response from the OIDC discovery endpoint and contains supported scopes and claims, public keys used to sign the tokens, issuer, and other information. Clients can use this information to construct a request to the OpenID server. The field names and values are defined in the OpenID Connect Discovery Specification https://openid.net/specs/openid-connect-discovery-1_0.html

type JWKSValidator

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

JWKSValidator provides public verification that JWT tokens have been issued by the Datum authentication service by checking that the tokens have been signed using public keys from a JSON Web Key Set (JWKS). The validator then returns Datum specific claims if the token is in fact valid.

func NewJWKSValidator

func NewJWKSValidator(keys jwk.Set, audience, issuer string) *JWKSValidator

NewJWKSValidator is a constructor for creating a new instance of the `JWKSValidator` struct. It takes in a `jwk.Set` containing the JSON Web Key Set (JWKS), as well as the audience and issuer strings. It initializes a new `JWKSValidator` with the provided JWKS, audience, and issuer

func (*JWKSValidator) Parse

func (v *JWKSValidator) Parse(tks string) (claims *Claims, err error)

Parse an access or refresh token verifying its signature but without verifying its claims. This ensures that valid JWT tokens are still accepted but claims can be handled on a case-by-case basis; for example by validating an expired access token during reauthentication

func (*JWKSValidator) Verify

func (v *JWKSValidator) Verify(tks string) (claims *Claims, err error)

Verify an access or a refresh token after parsing and return its claims.

type Keyfunc

type Keyfunc func(*Token) (interface{}, error)

Keyfunc will be used by the Parse methods as a callback function to supply the key for verification

type MockValidator

type MockValidator struct {
	OnVerify func(string) (*Claims, error)
	OnParse  func(string) (*Claims, error)
	Calls    map[string]int
}

func (*MockValidator) Parse

func (m *MockValidator) Parse(tks string) (*Claims, error)

func (*MockValidator) Verify

func (m *MockValidator) Verify(tks string) (*Claims, error)

type OrgInviteToken

type OrgInviteToken struct {
	Email string    `msgpack:"email"`
	OrgID ulid.ULID `msgpack:"organization_id"`
	SigningInfo
}

OrgInviteToken packages an email address with random data and an expiration time so that it can be serialized and hashed into a token which can be sent to users

func NewOrgInvitationToken

func NewOrgInvitationToken(email string, orgID ulid.ULID) (token *OrgInviteToken, err error)

NewOrgInvitationToken creates a token struct from an email address that expires in 14 days

func (*OrgInviteToken) Sign

func (t *OrgInviteToken) Sign() (string, []byte, error)

Sign creates a base64 encoded string from the token data so that it can be sent to users as part of a URL. The returned secret should be stored in the database so that the string can be recomputed when verifying a user provided token.

func (*OrgInviteToken) Verify

func (t *OrgInviteToken) Verify(signature string, secret []byte) (err error)

Verify checks that a token was signed with the secret and is not expired

type ParseError

type ParseError struct {
	Object string
	Value  string
	Err    error
}

ParseError is defining a custom error type called `ParseError`

func (*ParseError) Error

func (e *ParseError) Error() string

Error returns the ParseError in string format

type ResetToken

type ResetToken struct {
	UserID ulid.ULID `msgpack:"user_id"`
	SigningInfo
}

ResetToken packages a user ID with random data and an expiration time so that it can be serialized and hashed into a token which can be sent to users

func NewResetToken

func NewResetToken(id ulid.ULID) (token *ResetToken, err error)

NewResetToken creates a token struct from a user ID that expires in 15 minutes

func (*ResetToken) Sign

func (t *ResetToken) Sign() (string, []byte, error)

Sign creates a base64 encoded string from the token data so that it can be sent to users as part of a URL. The returned secret should be stored in the database so that the string can be recomputed when verifying a user provided token

func (*ResetToken) Verify

func (t *ResetToken) Verify(signature string, secret []byte) (err error)

Verify checks that a token was signed with the secret and is not expired

type SigningInfo

type SigningInfo struct {
	ExpiresAt time.Time `msgpack:"expires_at"`
	Nonce     []byte    `msgpack:"nonce"`
}

SigningInfo contains an expiration time and a nonce that is used to sign the token

func NewSigningInfo

func NewSigningInfo(expires time.Duration) (info SigningInfo, err error)

NewSigningInfo creates new signing info with a time expiration

func (SigningInfo) IsExpired

func (d SigningInfo) IsExpired() bool

type SigningMethod

type SigningMethod interface {
	// Verify returns nil if signature is valid
	Verify(signingString, signature string, key interface{}) error
	// Sign returns encoded signature or error
	Sign(signingString string, key interface{}) (string, error)
	// Alg returns the alg identifier for this method (example: 'HS256')
	Alg() string
}

SigningMethod can be used add new methods for signing or verifying tokens

func GetSigningMethod

func GetSigningMethod(alg string) (method SigningMethod)

GetSigningMethod retrieves a signing method from an "alg" string

type Token

type Token struct {
	// Raw is the raw token; populated when you parse a token
	Raw string
	// Method is the signing metehod of the token
	Method SigningMethod
	// Header is the first segment of the token
	Header map[string]interface{}
	// Claims is the second segment of the token
	Claims           Claims
	ClaimBytes       []byte
	ToBeSignedString string
	// Signature is the third segment of the token; populated when you parse a token
	Signature string
	// Valid is a bool determining if the token is valid; populated when you parse or verify a toekn
	Valid bool
}

Token represents a JWT Token. Different fields will be used depending on whether you're creating or parsing/verifying a token

type TokenManager

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

func New

func New(conf Config) (tm *TokenManager, err error)

New creates a TokenManager with the specified keys which should be a mapping of ULID strings to paths to files that contain PEM encoded RSA private keys. This input is specifically designed for the config environment variable so that keys can be loaded from k8s or vault secrets that are mounted as files on disk

func NewWithKey

func NewWithKey(key *rsa.PrivateKey, conf Config) (tm *TokenManager, err error)

NewWithKey is a constructor function that creates a new instance of the TokenManager struct with a specified RSA private key. It takes in the private key as a parameter and initializes the TokenManager with the provided key, along with other configuration settings from the TokenConfig struct. It returns the created TokenManager instance or an error if there was a problem initializing the TokenManager.

func (*TokenManager) Config

func (tm *TokenManager) Config() Config

Config returns the token manager config

func (*TokenManager) CreateAccessToken

func (tm *TokenManager) CreateAccessToken(claims *Claims) (_ *jwt.Token, err error)

CreateAccessToken from the credential payload or from an previous token if the access token is being reauthorized from previous credentials or an already issued access token

func (*TokenManager) CreateRefreshToken

func (tm *TokenManager) CreateRefreshToken(accessToken *jwt.Token) (refreshToken *jwt.Token, err error)

CreateRefreshToken from the Access token claims with predefined expiration

func (*TokenManager) CreateToken

func (tm *TokenManager) CreateToken(claims *Claims) *jwt.Token

CreateToken from the claims payload without modifying the claims unless the claims are missing required fields that need to be updated

func (*TokenManager) CreateTokenPair

func (tm *TokenManager) CreateTokenPair(claims *Claims) (accessToken, refreshToken string, err error)

CreateTokenPair returns signed access and refresh tokens for the specified claims in one step since usually you want both access and refresh tokens at the same time

func (*TokenManager) CurrentKey

func (tm *TokenManager) CurrentKey() ulid.ULID

CurrentKey returns the ulid of the current key being used to sign tokens - this is just the identifier of the key, not the key itself

func (*TokenManager) Keys

func (tm *TokenManager) Keys() (keys jwk.Set, err error)

Keys returns the JWKS with public keys for use externally

func (*TokenManager) Parse

func (v *TokenManager) Parse(tks string) (claims *Claims, err error)

Parse an access or refresh token verifying its signature but without verifying its claims. This ensures that valid JWT tokens are still accepted but claims can be handled on a case-by-case basis; for example by validating an expired access token during reauthentication

func (*TokenManager) RefreshAudience

func (tm *TokenManager) RefreshAudience() string

RefreshAudience returns the refresh audience for the token manager; The refresh audience in plain-human-speak is the URL where the refresh token should be sent for validation (which is our datum endpoint)

func (*TokenManager) Sign

func (tm *TokenManager) Sign(token *jwt.Token) (string, error)

Sign an access or refresh token and return the token

func (*TokenManager) Verify

func (v *TokenManager) Verify(tks string) (claims *Claims, err error)

Verify an access or a refresh token after parsing and return its claims.

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
}

TokenResponse is the request response when exchanging an oauth token from one provider to a Datum issued token

type ValidationError

type ValidationError struct {
	Inner  error
	Errors uint32
	// contains filtered or unexported fields
}

ValidationError represents an error from Parse if token is not valid

func NewValidationError

func NewValidationError(errorText string, errorFlags uint32) *ValidationError

NewValidationError is a helper for constructing a ValidationError with a string error message

func (ValidationError) Error

func (e ValidationError) Error() string

Error is the implementation of the err interface for ValidationError

func (*ValidationError) Is

func (e *ValidationError) Is(err error) bool

Is checks if this ValidationError is of the supplied error. We are first checking for the exact error message by comparing the inner error message. If that fails, we compare using the error flags. This way we can use custom error messages and leverage errors.Is using the global error variables, plus I just learned how to use errors.Is today so this is pretty sweet

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Unwrap gives errors.Is and errors.As access to the inner errors defined above

type Validator

type Validator interface {
	// Verify an access or a refresh token after parsing and return its claims
	Verify(tks string) (claims *Claims, err error)

	// Parse an access or refresh token without verifying claims (e.g. to check an expired token)
	Parse(tks string) (claims *Claims, err error)
}

Validator are able to verify that access and refresh tokens were issued by Datum and that their claims are valid (e.g. not expired).

type VerificationToken

type VerificationToken struct {
	Email string `msgpack:"email"`
	SigningInfo
}

VerificationToken packages an email address with random data and an expiration time so that it can be serialized and hashed into a token which can be sent to users

func NewVerificationToken

func NewVerificationToken(email string) (token *VerificationToken, err error)

NewVerificationToken creates a token struct from an email address that expires in 7 days

func (*VerificationToken) Sign

func (t *VerificationToken) Sign() (string, []byte, error)

Sign creates a base64 encoded string from the token data so that it can be sent to users as part of a URL. The returned secret should be stored in the database so that the string can be recomputed when verifying a user provided token.

func (*VerificationToken) Verify

func (t *VerificationToken) Verify(signature string, secret []byte) (err error)

Verify checks that a token was signed with the secret and is not expired

Jump to

Keyboard shortcuts

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