authkit

package module
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Dec 20, 2024 License: MIT Imports: 22 Imported by: 0

README

Toolkit for authentication and authorization in Go services

GoDoc Widget

Installation

go get -u github.com/acronis/go-authkit

Features

  • Authenticate HTTP requests with JWT tokens via middleware that can be configured via YAML/JSON file or environment variables.
  • Authorize HTTP requests with JWT tokens by verifying access based on the roles in the JWT claims.
  • Fetch and cache JSON Web Key Sets (JWKS) from Identity Providers (IDP).
  • Introspect Access Tokens via OAuth 2.0 Token Introspection endpoint.
  • Fetch and cache Access Tokens from Identity Providers (IDP).
  • Provides primitives for testing authentication and authorization in HTTP services.

Authenticate HTTP requests with JWT tokens

JWTAuthMiddleware() creates a middleware that authenticates requests with JWT tokens and puts the parsed JWT claims (jwt.Claims interface) into the request context.

jwt.Claims interface extends the jwt.Claims from the github.com/golang-jwt/jwt/v5 package with additional methods.

Its default implementation jwt.DefaultClaims is an extension of the RegisteredClaims struct from the github.com/golang-jwt/jwt/v5 package. jwt.DefaultClaims contains additional Scope field that represents a list of access policies. They are used for authorization in the typical Acronis service, and actually can be used in any other application that performs multi-tenant authorization.

package jwt

import (
	jwtgo "github.com/golang-jwt/jwt/v5"
)

// Scope is a slice of access policies.
type Scope []AccessPolicy

// Claims is an interface that extends jwt.Claims from the "github.com/golang-jwt/jwt/v5"
// with additional methods for working with access policies.
type Claims interface {
	jwtgo.Claims

	// GetID returns the JTI field of the claims.
	GetID() string

	// GetScope returns the scope of the claims as a slice of access policies.
	GetScope() Scope

	// Clone returns a deep copy of the claims.
	Clone() Claims

	// ApplyScopeFilter filters (in-place) the scope of the claims by the specified filter.
	ApplyScopeFilter(filter ScopeFilter)
}

// DefaultClaims is a struct that extends jwt.RegisteredClaims with a custom scope field.
// It may be embedded into custom claims structs if additional fields are required.
type DefaultClaims struct {
	jwtgo.RegisteredClaims
	Scope Scope `json:"scope,omitempty"`
}

// AccessPolicy represents a single access policy which specifies access rights to a tenant or resource
// in the scope of a resource server.
type AccessPolicy struct {
	// TenantID is a unique identifier of tenant for which access is granted (if resource is not specified)
	// or which the resource is owned by (if resource is specified).
	TenantID string `json:"tid,omitempty"`

	// TenantUUID is a UUID of tenant for which access is granted (if the resource is not specified)
	// or which the resource is owned by (if the resource is specified).
	TenantUUID string `json:"tuid,omitempty"`

	// ResourceServerID is a unique resource server instance or cluster ID.
	ResourceServerID string `json:"rs,omitempty"`

	// ResourceNamespace is a namespace to which resource belongs within resource server.
	// E.g.: account-server, storage-manager, task-manager, alert-manager, etc.
	ResourceNamespace string `json:"rn,omitempty"`

	// ResourcePath is a unique identifier of or path to a single resource or resource collection
	// in the scope of the resource server and namespace.
	ResourcePath string `json:"rp,omitempty"`

	// Role determines what actions are allowed to be performed on the specified tenant or resource.
	Role string `json:"role,omitempty"`
}

JWTAuthMiddleware() function accepts two mandatory arguments: errorDomain and JWTParser.

The errorDomain is usually the name of the service that uses the middleware, and it's goal is distinguishing errors from different services. It helps to understand where the error occurred and what service caused it. For example, if the "Authorization" HTTP header is missing, the middleware will return 401 with the following response body:

{
  "error": {
    "domain": "MyService",
    "code": "bearerTokenMissing",
    "message": "Authorization bearer token is missing."
  }
}

JWTParser is used to parse and validate JWT tokens. It can be constructed with the NewJWTParser right from the YAML/JSON configuration or with the specific jwt.NewParser()/jwt.NewCachingParser() functions (both of them are used in the NewJWTParser() under the hood depending on the configuration). jwt.CachingParser uses LRU in-memory cache for the JWT claims to avoid parsing and validating the same token multiple times that can be useful when JWT tokens are large and the service gets a lot of requests from the same client. NewJWTParser() uses jwks.CachingClient for fetching and caching JWKS (JSON Web Key Set) that is used for verifying JWT tokens. This client performs <issuer_url>/.well-known/openid-configuration request to get the JWKS URL ("jwks_uri" field) and fetches JWKS from there. Issuer should be presented in the trusted list, otherwise the middleware will return HTTP response with 401 status code and log a corresponding error message.

Authentication middleware example

Example of the HTTP middleware that authenticates requests with JWT tokens can be found here.

Introspect Access Tokens via the OAuth 2.0 Token Introspection endpoint

Introspection is the process of determining the active state of an access token and the associated metadata. More information can be found in the RFC 7662.

go-authkit provides a way to introspect any kind of access tokens, not only JWT tokens, via the OAuth 2.0 Token Introspection endpoint. It performs unmarsalling of the response from the endpoint to the idptoken.IntrospectionResult struct which contains the Active field that indicates whether the token is active or not. Additionally, it contains the TokenType field that specifies the type of the token and the Claims field for presenting the token's metadata in the form of JWT claims.

package idptoken

import (
	"github.com/acronis/go-authkit/jwt"
)

type IntrospectionResult struct {
	Active    bool   `json:"active"`
	TokenType string `json:"token_type,omitempty"`
	jwt.Claims
}

The Token Introspection endpoint may be configured statically or obtained from the OpenID Connect Discovery response (GET /.well-known/openid-configuration request for the issuer URL). In case of the static configuration, gRPC could be used instead of HTTP for the introspection request (see idp_token.proto for details).

NewTokenIntrospector() function creates an introspector that can be used to introspect access tokens.

It's a good practice to protect the introspection endpoint itself. That's why NewTokenIntrospector() accepts the Token Provider (TokenProvider interface) that is used to get the Access Token (usually from the Identity Provider) to perform the introspection request with it. Please keep in mind that the Token Provider should return a valid Access Token that has the necessary permissions to perform the introspection request.

Additionally, the NewTokenIntrospector() accepts the scope filter for filtering out the unnecessary claims from the introspection response.

Introspection example

Example of the access token introspection during the HTTP request authentication can be found here.

Fetching and caching Access Tokens from Identity Providers

The idptoken.Provider object is used to fetch and cache Access Tokens from Identity Providers (IDP).

Example:

package main

import (
	"log"
	"net/http"
	
    "github.com/acronis/go-authkit/idptoken"
)

func main() {
	// ...
	httpClient := &http.Client{Timeout: 30 * time.Second}
	source := idptoken.Source{
		URL:          idpURL,
		ClientID:     clientID,
		ClientSecret: clientSecret,
	}
	provider := idptoken.NewProvider(httpClient, source)
	accessToken, err := provider.GetToken(ctx)
	if err != nil {
		log.Fatalf("failed to get access token: %v", err)
	}
	// ...
}

License

Copyright © 2024 Acronis International GmbH.

Licensed under MIT License.

Documentation

Overview

Package authkit provides high-level helpers and basic objects for authN/authZ.

Index

Constants

View Source
const HeaderAuthorization = "Authorization"

HeaderAuthorization contains the name of HTTP header with data that is used for authentication and authorization.

Variables

View Source
var (
	ErrCodeBearerTokenMissing   = "bearerTokenMissing"
	ErrCodeAuthenticationFailed = "authenticationFailed"
	ErrCodeAuthorizationFailed  = "authorizationFailed"
)

Authentication and authorization error codes. We are using "var" here because some services may want to use different error codes.

View Source
var (
	ErrMessageBearerTokenMissing   = "Authorization bearer token is missing."
	ErrMessageAuthenticationFailed = "Authentication is failed."
	ErrMessageAuthorizationFailed  = "Authorization is failed."
)

Authentication error messages. We are using "var" here because some services may want to use different error messages.

Functions

func GetBearerTokenFromContext

func GetBearerTokenFromContext(ctx context.Context) string

GetBearerTokenFromContext extracts token from the context.

func GetBearerTokenFromRequest

func GetBearerTokenFromRequest(r *http.Request) string

GetBearerTokenFromRequest extracts jwt token from request headers.

func GetJWTClaimsFromContext

func GetJWTClaimsFromContext(ctx context.Context) jwt.Claims

GetJWTClaimsFromContext extracts JWT claims from the context.

func JWTAuthMiddleware

func JWTAuthMiddleware(errorDomain string, jwtParser JWTParser, opts ...JWTAuthMiddlewareOption) func(next http.Handler) http.Handler

JWTAuthMiddleware is a middleware that does authentication by Access Token from the "Authorization" HTTP header of incoming request. errorDomain is used for error responses. It is usually the name of the service that uses the middleware, and its goal is distinguishing errors from different services. It helps to understand where the error occurred and what service caused it. For example, if the "Authorization" HTTP header is missing, the middleware will return 401 with the following response body:

{"error": {"domain": "MyService", "code": "bearerTokenMissing", "message": "Authorization bearer token is missing."}}

func NewContextWithBearerToken

func NewContextWithBearerToken(ctx context.Context, token string) context.Context

NewContextWithBearerToken creates a new context with token.

func NewContextWithJWTClaims

func NewContextWithJWTClaims(ctx context.Context, jwtClaims jwt.Claims) context.Context

NewContextWithJWTClaims creates a new context with JWT claims.

func NewTokenIntrospector

func NewTokenIntrospector(
	cfg *Config,
	tokenProvider idptoken.IntrospectionTokenProvider,
	scopeFilter jwt.ScopeFilter,
	opts ...TokenIntrospectorOption,
) (*idptoken.Introspector, error)

NewTokenIntrospector creates a new TokenIntrospector with the given configuration, token provider and scope filter. If cfg.Introspection.ClaimsCache.Enabled or cfg.Introspection.NegativeCache.Enabled is true, then idptoken.CachingIntrospector created, otherwise - idptoken.Introspector. Please note that the tokenProvider should be able to provide access token with the policy for introspection. scopeFilter is a list of filters that will be applied to the introspected token.

func NewVerifyAccessByRolesInJWT

func NewVerifyAccessByRolesInJWT(roles ...Role) func(r *http.Request, claims jwt.Claims) bool

NewVerifyAccessByRolesInJWT creates a new function which may be used for verifying access by roles in JWT scope.

func NewVerifyAccessByRolesInJWTMaker

func NewVerifyAccessByRolesInJWTMaker(namespace string) func(roleNames ...string) func(r *http.Request, claims jwt.Claims) bool

NewVerifyAccessByRolesInJWTMaker creates a new function which may be used for verifying access by roles in JWT scope given a namespace.

func SetDefaultLogger added in v0.7.0

func SetDefaultLogger(logger log.FieldLogger)

SetDefaultLogger sets the default logger for the library.

Types

type CachingJWTParser

type CachingJWTParser interface {
	JWTParser
	InvalidateCache(ctx context.Context)
}

CachingJWTParser does the same as JWTParser but stores parsed JWT claims in cache.

type ClaimsCacheConfig

type ClaimsCacheConfig struct {
	Enabled    bool
	MaxEntries int
}

ClaimsCacheConfig is a configuration of how claims cache will be used.

type Config

type Config struct {
	HTTPClient HTTPClientConfig
	GRPCClient GRPCClientConfig

	JWT           JWTConfig
	JWKS          JWKSConfig
	Introspection IntrospectionConfig
	// contains filtered or unexported fields
}

Config represents a set of configuration parameters for authentication and authorization.

func NewConfig

func NewConfig() *Config

NewConfig creates a new instance of the Config.

func NewConfigWithKeyPrefix

func NewConfigWithKeyPrefix(keyPrefix string) *Config

NewConfigWithKeyPrefix creates a new instance of the Config. Allows specifying key prefix which will be used for parsing configuration parameters.

func (*Config) KeyPrefix

func (c *Config) KeyPrefix() string

KeyPrefix returns a key prefix with which all configuration parameters should be presented.

func (*Config) Set

func (c *Config) Set(dp config.DataProvider) error

Set sets auth configuration values from config.DataProvider.

func (*Config) SetProviderDefaults

func (c *Config) SetProviderDefaults(dp config.DataProvider)

SetProviderDefaults sets default configuration values for auth in config.DataProvider.

type GRPCClientConfig

type GRPCClientConfig struct {
	RequestTimeout time.Duration
}

type GRPCTLSConfig

type GRPCTLSConfig struct {
	Enabled    bool
	CACert     string
	ClientCert string
	ClientKey  string
}

GRPCTLSConfig is a configuration of how gRPC connection will be secured.

type HTTPClientConfig

type HTTPClientConfig struct {
	RequestTimeout time.Duration
}

type IntrospectionCacheConfig

type IntrospectionCacheConfig struct {
	Enabled    bool
	MaxEntries int
	TTL        time.Duration
}

IntrospectionCacheConfig is a configuration of how claims cache will be used for introspection.

type IntrospectionConfig

type IntrospectionConfig struct {
	Enabled bool

	Endpoint         string
	AccessTokenScope []string

	ClaimsCache            IntrospectionCacheConfig
	NegativeCache          IntrospectionCacheConfig
	EndpointDiscoveryCache IntrospectionCacheConfig

	GRPC IntrospectionGRPCConfig
}

IntrospectionConfig is a configuration of how token introspection will be used.

type IntrospectionGRPCConfig

type IntrospectionGRPCConfig struct {
	Endpoint       string
	RequestTimeout time.Duration
	TLS            GRPCTLSConfig
}

IntrospectionGRPCConfig is a configuration of how token will be introspected via gRPC.

type JWKSConfig

type JWKSConfig struct {
	Cache struct {
		UpdateMinInterval time.Duration
	}
}

JWKSConfig is configuration of how JWKS will be used.

type JWTAuthMiddlewareOption

type JWTAuthMiddlewareOption func(options *jwtAuthMiddlewareOpts)

JWTAuthMiddlewareOption is an option for JWTAuthMiddleware.

func WithJWTAuthMiddlewareLoggerProvider added in v0.7.0

func WithJWTAuthMiddlewareLoggerProvider(loggerProvider func(ctx context.Context) log.FieldLogger) JWTAuthMiddlewareOption

WithJWTAuthMiddlewareLoggerProvider is an option to set a logger provider for JWTAuthMiddleware.

func WithJWTAuthMiddlewarePrometheusLibInstanceLabel added in v0.12.0

func WithJWTAuthMiddlewarePrometheusLibInstanceLabel(label string) JWTAuthMiddlewareOption

WithJWTAuthMiddlewarePrometheusLibInstanceLabel is an option to set a label for Prometheus metrics that are used by JWTAuthMiddleware.

func WithJWTAuthMiddlewareTokenIntrospector

func WithJWTAuthMiddlewareTokenIntrospector(tokenIntrospector TokenIntrospector) JWTAuthMiddlewareOption

WithJWTAuthMiddlewareTokenIntrospector is an option to set a token introspector for JWTAuthMiddleware.

func WithJWTAuthMiddlewareVerifyAccess

func WithJWTAuthMiddlewareVerifyAccess(verifyAccess func(r *http.Request, claims jwt.Claims) bool) JWTAuthMiddlewareOption

WithJWTAuthMiddlewareVerifyAccess is an option to set a function that verifies access for JWTAuthMiddleware.

type JWTConfig

type JWTConfig struct {
	TrustedIssuers    map[string]string
	TrustedIssuerURLs []string
	RequireAudience   bool
	ExpectedAudience  []string
	ClaimsCache       ClaimsCacheConfig
}

JWTConfig is configuration of how JWT will be verified.

type JWTParser

type JWTParser interface {
	Parse(ctx context.Context, token string) (jwt.Claims, error)
}

JWTParser is an interface for parsing string representation of JWT.

func NewJWTParser

func NewJWTParser(cfg *Config, opts ...JWTParserOption) (JWTParser, error)

NewJWTParser creates a new JWTParser with the given configuration. If cfg.JWT.ClaimsCache.Enabled is true, then jwt.CachingParser created, otherwise - jwt.Parser.

type JWTParserOption

type JWTParserOption func(options *jwtParserOptions)

JWTParserOption is an option for creating JWTParser.

func WithJWTParserClaimsTemplate added in v0.10.0

func WithJWTParserClaimsTemplate(claimsTemplate jwt.Claims) JWTParserOption

WithJWTParserClaimsTemplate sets the claims template for JWTParser.

func WithJWTParserLoggerProvider added in v0.7.0

func WithJWTParserLoggerProvider(loggerProvider func(ctx context.Context) log.FieldLogger) JWTParserOption

WithJWTParserLoggerProvider sets the logger provider for JWTParser.

func WithJWTParserPrometheusLibInstanceLabel

func WithJWTParserPrometheusLibInstanceLabel(label string) JWTParserOption

WithJWTParserPrometheusLibInstanceLabel sets the Prometheus lib instance label for JWTParser.

func WithJWTParserScopeFilter added in v0.13.0

func WithJWTParserScopeFilter(scopeFilter jwt.ScopeFilter) JWTParserOption

WithJWTParserScopeFilter sets the scope filter for JWTParser. If it's used, then only access policies in scope that match at least one of the filtering policies will be returned. It's useful when the claims cache is used (cfg.JWT.ClaimsCache.Enabled is true), and we want to store only some of the access policies in the cache to reduce memory usage.

func WithJWTParserTrustedIssuerNotFoundFallback

func WithJWTParserTrustedIssuerNotFoundFallback(fallback jwt.TrustedIssNotFoundFallback) JWTParserOption

WithJWTParserTrustedIssuerNotFoundFallback sets the fallback for JWTParser when trusted issuer is not found.

type Role

type Role struct {
	Namespace string
	Name      string
}

Role is a representation of role which may be used for verifying access.

type TokenIntrospector

type TokenIntrospector interface {
	IntrospectToken(ctx context.Context, token string) (idptoken.IntrospectionResult, error)
}

TokenIntrospector is an interface for introspecting tokens.

type TokenIntrospectorOption

type TokenIntrospectorOption func(options *tokenIntrospectorOptions)

TokenIntrospectorOption is an option for creating TokenIntrospector.

func WithTokenIntrospectorLoggerProvider added in v0.7.0

func WithTokenIntrospectorLoggerProvider(loggerProvider func(ctx context.Context) log.FieldLogger) TokenIntrospectorOption

WithTokenIntrospectorLoggerProvider sets the logger provider for TokenIntrospector.

func WithTokenIntrospectorPrometheusLibInstanceLabel

func WithTokenIntrospectorPrometheusLibInstanceLabel(label string) TokenIntrospectorOption

WithTokenIntrospectorPrometheusLibInstanceLabel sets the Prometheus lib instance label for TokenIntrospector.

func WithTokenIntrospectorResultTemplate added in v0.10.0

func WithTokenIntrospectorResultTemplate(resultTemplate idptoken.IntrospectionResult) TokenIntrospectorOption

WithTokenIntrospectorResultTemplate sets the result template for TokenIntrospector.

func WithTokenIntrospectorTrustedIssuerNotFoundFallback

func WithTokenIntrospectorTrustedIssuerNotFoundFallback(
	fallback idptoken.TrustedIssNotFoundFallback,
) TokenIntrospectorOption

WithTokenIntrospectorTrustedIssuerNotFoundFallback sets the fallback for TokenIntrospector when trusted issuer is not found.

Directories

Path Synopsis
examples
Package idptest provides helper primitives and functions required for testing signing and key generation and a simple HTTP server with JWKS, issuer and IDP configuration endpoints.
Package idptest provides helper primitives and functions required for testing signing and key generation and a simple HTTP server with JWKS, issuer and IDP configuration endpoints.
Package idptoken provides a robust way to request access tokens from IDP.
Package idptoken provides a robust way to request access tokens from IDP.
pb
internal
idputil
Package idputil provides utilities for working with identity providers.
Package idputil provides utilities for working with identity providers.
libinfo
Package libinfo provides helpers for working with the library information.
Package libinfo provides helpers for working with the library information.
metrics
Package metrics provides helpers for working with the library metrics.
Package metrics provides helpers for working with the library metrics.
testing
Package testing provides internal testing utilities.
Package testing provides internal testing utilities.
Package jwks contains clients for getting public keys from JWKS.
Package jwks contains clients for getting public keys from JWKS.
Package jwt provides primitives for working with JWT (Parser, Claims, and so on).
Package jwt provides primitives for working with JWT (Parser, Claims, and so on).

Jump to

Keyboard shortcuts

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