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
- Variables
- func ActiveTeam(c *fiber.Ctx, opts ...Option) string
- func Claims(c *fiber.Ctx, opts ...Option) (jwt.MapClaims, bool)
- func Compare(hash, password string) error
- func HasScope(c *fiber.Ctx, scope string, opts ...Option) bool
- func Hash(password string) (string, error)
- func Optional(secret string, opts ...Option) fiber.Handler
- func RequireAuth(secret string, opts ...Option) fiber.Handler
- func RequireRole(roles ...string) fiber.Handler
- func RequireScope(scope string, opts ...Option) fiber.Handler
- func RequireTeam(opts ...Option) fiber.Handler
- func Scopes(c *fiber.Ctx, opts ...Option) []string
- func Subject(c *fiber.Ctx, opts ...Option) string
- func TeamRole(c *fiber.Ctx, opts ...Option) string
- type Issuer
- type IssuerOption
- type MemoryStore
- func (m *MemoryStore) Block(_ context.Context, jti string, ttl time.Duration) error
- func (m *MemoryStore) Family(_ context.Context, fid string) (string, bool, error)
- func (m *MemoryStore) IsBlocked(_ context.Context, jti string) (bool, error)
- func (m *MemoryStore) RevokeFamily(_ context.Context, fid string) error
- func (m *MemoryStore) SetFamily(_ context.Context, fid, jti string, ttl time.Duration) error
- type Option
- type RedisStore
- func (s *RedisStore) Block(ctx context.Context, jti string, ttl time.Duration) error
- func (s *RedisStore) Family(ctx context.Context, fid string) (string, bool, error)
- func (s *RedisStore) IsBlocked(ctx context.Context, jti string) (bool, error)
- func (s *RedisStore) RevokeFamily(ctx context.Context, fid string) error
- func (s *RedisStore) SetFamily(ctx context.Context, fid, jti string, ttl time.Duration) error
- type StoreOption
- type TokenPair
- type TokenStore
Constants ¶
const DefaultClaimsKey = "auth_claims"
DefaultClaimsKey is the Fiber locals key under which the auth middleware stores validated claims.
Variables ¶
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 ¶
ActiveTeam returns the active team id from the "team" claim ("" if absent or not a string).
func Compare ¶
Compare reports whether password matches the bcrypt hash; it returns nil on a match and a non-nil error otherwise.
func HasScope ¶
HasScope reports whether the request's claims include scope. It reads the same "scopes" claim as Scopes and RequireScope.
func Hash ¶
Hash returns the bcrypt hash of password using the default cost. Note bcrypt only considers the first 72 bytes of the input.
func Optional ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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) RevokeFamily ¶
func (m *MemoryStore) RevokeFamily(_ context.Context, fid string) error
RevokeFamily removes the family record, invalidating all its tokens.
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 ¶
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 ¶
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) RevokeFamily ¶
func (s *RedisStore) RevokeFamily(ctx context.Context, fid string) error
RevokeFamily removes the family record, invalidating all its tokens.
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.