auth

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package auth provides bcrypt password hashing and JWT bearer authentication middleware (with scope checks) for Fiber apps, built on the jwt, context, and response packages.

Index

Constants

View Source
const DefaultClaimsKey = "auth_claims"

DefaultClaimsKey is the Fiber locals key under which the auth middleware stores validated claims.

Variables

View Source
var (
	ErrInvalidToken = errors.New("auth: invalid token")
	ErrTokenReuse   = errors.New("auth: refresh token reuse detected")
)

Errors returned by the Issuer. Callers should map both to HTTP 401; ErrTokenReuse additionally signals a likely stolen refresh token (the whole token family is revoked when it occurs).

Functions

func ActiveTeam

func ActiveTeam(c *fiber.Ctx, opts ...Option) string

ActiveTeam returns the active team id from the "team" claim ("" if absent or not a string).

func Claims

func Claims(c *fiber.Ctx, opts ...Option) (jwt.MapClaims, bool)

Claims returns the claims stored by the auth middleware.

func Compare

func Compare(hash, password string) error

Compare reports whether password matches the bcrypt hash; it returns nil on a match and a non-nil error otherwise.

func HasScope

func HasScope(c *fiber.Ctx, scope string, opts ...Option) bool

HasScope reports whether the request's claims include scope. It reads the same "scopes" claim as Scopes and RequireScope.

func Hash

func Hash(password string) (string, error)

Hash returns the bcrypt hash of password using the default cost. Note bcrypt only considers the first 72 bytes of the input.

func Optional

func Optional(secret string, opts ...Option) fiber.Handler

Optional behaves like RequireAuth but proceeds without claims when the token is missing or invalid. With WithBlocklist, a blocked or errored token is treated as no auth (the handler runs anonymously) rather than rejected.

func RequireAuth

func RequireAuth(secret string, opts ...Option) fiber.Handler

RequireAuth extracts a bearer token, validates it with secret, stores the claims in the request context, then calls the next handler. A missing or invalid token gets a 401 response. Pass WithBlocklist to also reject revoked tokens by jti (fail-closed: a store error is treated as unauthorized). Tokens with no jti field (not minted by an Issuer) are not subject to the blocklist.

func RequireRole

func RequireRole(roles ...string) fiber.Handler

RequireRole ensures the caller's active-team role is one of roles (use after RequireAuth). It responds 401 when there are no claims and 403 when the role does not match. RequireRole reads the default claims context key; if you use WithContextKey, gate with permission checks (RequireScope) instead.

func RequireScope

func RequireScope(scope string, opts ...Option) fiber.Handler

RequireScope ensures the authenticated claims include scope (use after RequireAuth). It responds 401 when there are no claims and 403 when the scope is missing. If you pass WithContextKey to RequireAuth, pass the same option here so both use the same claims key.

func RequireTeam

func RequireTeam(opts ...Option) fiber.Handler

RequireTeam ensures an active team is present (use after RequireAuth). It responds 401 when there are no claims and 403 when no team is active.

func Scopes

func Scopes(c *fiber.Ctx, opts ...Option) []string

Scopes reads the "scopes" claim (a JSON array of strings) set when the token was issued; it normalizes the JWT-decoded []any (or []string) form to []string. This library's RequireScope, HasScope, and Scopes all read that key.

func Subject

func Subject(c *fiber.Ctx, opts ...Option) string

Subject returns claims["sub"] as a string ("" if absent or not a string).

func TeamRole

func TeamRole(c *fiber.Ctx, opts ...Option) string

TeamRole returns the caller's role in the active team from the "role" claim ("" if absent or not a string).

Types

type Issuer

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

Issuer mints, rotates, and revokes JWT access/refresh token pairs backed by a TokenStore for revocation and refresh-family tracking.

func NewIssuer

func NewIssuer(secret string, store TokenStore, opts ...IssuerOption) *Issuer

NewIssuer creates an Issuer signing with secret and persisting revocation / family state in store.

func (*Issuer) Issue

func (i *Issuer) Issue(ctx context.Context, claims jwt.MapClaims) (TokenPair, error)

Issue mints a fresh access+refresh pair for a new login, allocating a new token family. claims must contain a non-empty string "sub"; "email" and "scopes" are copied into both tokens when present.

func (*Issuer) Logout

func (i *Issuer) Logout(ctx context.Context, accessToken, refreshToken string) error

Logout revokes a session. It blocks the access token's jti until its expiry and revokes the refresh token's family. Either argument may be empty; malformed or invalid tokens are ignored so logout is best-effort and never fails on junk input. It returns the first store error encountered, if any.

func (*Issuer) Refresh

func (i *Issuer) Refresh(ctx context.Context, refreshToken string) (TokenPair, error)

Refresh validates a refresh token and rotates it: it returns a new pair under the same family with fresh ids and updates the family's active token. If the presented token has been superseded (reuse), it revokes the entire family and returns ErrTokenReuse; any other validation failure returns ErrInvalidToken.

Reuse detection is sequential: once a token is rotated, presenting the old token again is caught by the family-pointer mismatch (the primary defense). The check-and-rotate is not atomic, so two Refresh calls racing on the *same* token may both succeed; last-write-wins on the family pointer makes the loser's token immediately stale, so the next use of it kills the family. If you need strict single-use under concurrent presentation, back the store with an atomic compare-and-swap implementation.

type IssuerOption

type IssuerOption func(*Issuer)

IssuerOption configures an Issuer.

func WithAccessTTL

func WithAccessTTL(d time.Duration) IssuerOption

WithAccessTTL sets the access token lifetime (default 15m).

func WithRefreshTTL

func WithRefreshTTL(d time.Duration) IssuerOption

WithRefreshTTL sets the refresh token lifetime (default 7 days).

type MemoryStore

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

MemoryStore is an in-process TokenStore for tests and single-node development. It is NOT suitable for multi-instance deployments because state is not shared across processes. Entries expire lazily on read; there is no background sweep.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore creates an empty in-memory token store.

func (*MemoryStore) Block

func (m *MemoryStore) Block(_ context.Context, jti string, ttl time.Duration) error

Block marks jti revoked until ttl elapses.

func (*MemoryStore) Family

func (m *MemoryStore) Family(_ context.Context, fid string) (string, bool, error)

Family returns the active refresh jti for fid.

func (*MemoryStore) IsBlocked

func (m *MemoryStore) IsBlocked(_ context.Context, jti string) (bool, error)

IsBlocked reports whether jti is currently revoked.

func (*MemoryStore) RevokeFamily

func (m *MemoryStore) RevokeFamily(_ context.Context, fid string) error

RevokeFamily removes the family record, invalidating all its tokens.

func (*MemoryStore) SetFamily

func (m *MemoryStore) SetFamily(_ context.Context, fid, jti string, ttl time.Duration) error

SetFamily records jti as the active refresh token for family fid until ttl.

type Option

type Option func(*config)

Option configures the auth middleware and accessors.

func WithBlocklist

func WithBlocklist(store TokenStore) Option

WithBlocklist makes RequireAuth and Optional reject access tokens whose jti has been revoked via store (e.g. by Issuer.Logout or Issuer.Refresh). Tokens without a jti (not minted by an Issuer) are not affected. Without this option the middleware performs no store lookups.

func WithContextKey

func WithContextKey(key string) Option

WithContextKey sets the Fiber locals key used to store and read claims (default DefaultClaimsKey).

type RedisStore

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

RedisStore is a redis-backed TokenStore. It uses the underlying go-redis client for native key operations (string set with expiry, existence checks, delete).

func NewRedisStore

func NewRedisStore(r *fhredis.Redis, opts ...StoreOption) *RedisStore

NewRedisStore creates a redis-backed token store on top of the redis package.

func (*RedisStore) Block

func (s *RedisStore) Block(ctx context.Context, jti string, ttl time.Duration) error

Block marks jti revoked until ttl elapses. A non-positive ttl is a no-op (an already-expired token needs no blocking) and avoids creating a key with no expiry, matching MemoryStore's behavior.

func (*RedisStore) Family

func (s *RedisStore) Family(ctx context.Context, fid string) (string, bool, error)

Family returns the active refresh jti for fid.

func (*RedisStore) IsBlocked

func (s *RedisStore) IsBlocked(ctx context.Context, jti string) (bool, error)

IsBlocked reports whether jti is currently revoked.

func (*RedisStore) RevokeFamily

func (s *RedisStore) RevokeFamily(ctx context.Context, fid string) error

RevokeFamily removes the family record, invalidating all its tokens.

func (*RedisStore) SetFamily

func (s *RedisStore) SetFamily(ctx context.Context, fid, jti string, ttl time.Duration) error

SetFamily records jti as the active refresh token for family fid until ttl. A non-positive ttl is a no-op (avoids creating a key with no expiry).

type StoreOption

type StoreOption func(*RedisStore)

StoreOption configures a RedisStore.

func WithStorePrefix

func WithStorePrefix(prefix string) StoreOption

WithStorePrefix sets the redis key prefix (default "auth:").

type TokenPair

type TokenPair struct {
	AccessToken      string `json:"access_token"`
	RefreshToken     string `json:"refresh_token"`
	TokenType        string `json:"token_type"`
	ExpiresIn        int64  `json:"expires_in"`         // access token lifetime, seconds
	RefreshExpiresIn int64  `json:"refresh_expires_in"` // refresh token lifetime, seconds
}

TokenPair is the access+refresh token pair returned by Issue and Refresh.

type TokenStore

type TokenStore interface {
	// Block marks jti revoked until ttl elapses.
	Block(ctx context.Context, jti string, ttl time.Duration) error
	// IsBlocked reports whether jti is currently revoked.
	IsBlocked(ctx context.Context, jti string) (bool, error)
	// SetFamily records jti as the active refresh token for family fid until ttl.
	SetFamily(ctx context.Context, fid, jti string, ttl time.Duration) error
	// Family returns the active refresh jti for fid; ok is false when the family
	// is absent or revoked.
	Family(ctx context.Context, fid string) (jti string, ok bool, err error)
	// RevokeFamily removes the family record, invalidating all its tokens.
	RevokeFamily(ctx context.Context, fid string) error
}

TokenStore tracks revoked token ids (blocklist) and the active refresh token per family, enabling logout, revocation, and refresh-token rotation with reuse detection. Implementations must be safe for concurrent use.

Jump to

Keyboard shortcuts

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