oidcauth

package
v0.1.0 Latest Latest
Warning

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

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

README

oidcauth

OpenID Connect token verification for Go, with optional in-memory caching and role-based authorization helpers. Designed for Keycloak but compatible with any OIDC-compliant provider.

Features

  • JWT verification (signature, issuer, audience, expiry) via go-oidc
  • RFC 7662 token introspection to detect server-side revocation
  • Pluggable cache via the Cache interface ship with MemoryCache or bring your own (Redis, Memcached, etc.)
  • Keycloak client-role helpers via HasRole
  • Safe for concurrent use

Installation

go get github.com/raykavin/gobox/oidcauth

Usage

Without cache

Every call to Verify performs a full JWT check plus a remote introspection request.

verifier, err := oidcauth.New(ctx, oidcauth.Config{
    RealmURL:     "https://keycloak.example.com/realms/main",
    ClientID:     "my-app",
    ClientSecret: "secret",
})
if err != nil {
    log.Fatal(err)
}

claims, err := verifier.Verify(ctx, bearerToken)
if err != nil {
    log.Fatal(err)
}

fmt.Println(claims.PreferredUsername)
With MemoryCache

Cache verified claims to avoid a network round-trip on every request.

cache := oidcauth.NewMemoryCache(ctx, oidcauth.DefaultCacheDuration) // 5m TTL
defer cache.Close()

verifier, err := oidcauth.New(ctx, config, oidcauth.WithCache(cache))

The entry TTL is min(token.exp, now + duration) the cache never serves a token past its own expiry.

With a custom cache backend

Implement the Cache interface to use any external store.

type Cache interface {
    Get(key string, now time.Time) (Claims, bool)
    Set(key string, claims Claims, now time.Time)
}
verifier, err := oidcauth.New(ctx, config, oidcauth.WithCache(myRedisCache))
Role-based authorization

HasRole checks for a Keycloak client role inside resource_access[clientID].roles.

if !verifier.HasRole(claims, "admin") {
    http.Error(w, "forbidden", http.StatusForbidden)
    return
}
Error handling

All errors wrap a package-level sentinel and can be inspected with errors.Is.

claims, err := verifier.Verify(ctx, token)
switch {
case errors.Is(err, oidcauth.ErrTokenRevoked):
    // token was revoked server-side
case errors.Is(err, oidcauth.ErrTokenValidationFailed):
    // signature / expiry / audience check failed
case errors.Is(err, oidcauth.ErrIntrospectionFailed):
    // could not reach the introspection endpoint
case err != nil:
    // unexpected error
}
Sentinel Cause
ErrInvalidRealmURL RealmURL is empty or not a valid HTTP(S) URL
ErrEmptyClientID ClientID is empty
ErrProviderInitFailed OIDC discovery request failed
ErrTokenValidationFailed JWT signature, issuer, audience, or expiry check failed
ErrIntrospectionFailed Introspection endpoint unreachable or returned unexpected status
ErrTokenRevoked Token is valid but marked inactive by the provider

Configuration

oidcauth.Config{
    RealmURL:     "https://keycloak.example.com/realms/main", // required
    ClientID:     "my-app",                                   // required
    ClientSecret: "secret",                                   // required for introspection
    RequestTimeout: 10 * time.Second,                         // default: 30s
    // test-only flags do not use in production
    SkipIssuerCheck:   false,
    SkipClientIDCheck: false,
    SkipExpiryCheck:   false,
}

Security notes

  • Cache keys are SHA-256 hashes of the raw bearer token raw tokens are never stored in memory as map keys.
  • SkipIssuerCheck, SkipClientIDCheck, and SkipExpiryCheck are intended for testing only. Enabling them in production disables core JWT security checks.
  • Revoked tokens remain valid for up to CacheDuration when caching is enabled. Choose a TTL that matches your revocation latency requirements.

Documentation

Overview

Package oidcauth provides OpenID Connect token verification with optional in-memory caching and role-based authorization helpers.

Basic usage

verifier, err := oidcauth.New(ctx, oidcauth.Config{
    RealmURL:     "https://keycloak.example.com/realms/main",
    ClientID:     "my-app",
    ClientSecret: "secret",
})
if err != nil {
    log.Fatal(err)
}

claims, err := verifier.Verify(ctx, bearerToken)
if err != nil {
    // handle errors.Is(err, oidcauth.ErrTokenRevoked), etc.
}

Caching

By default no cache is used and every call to Verify hits the provider. Attach a MemoryCache to avoid redundant network round-trips:

cache := oidcauth.NewMemoryCache(ctx, oidcauth.DefaultCacheDuration)
defer cache.Close()

verifier, err := oidcauth.New(ctx, config, oidcauth.WithCache(cache))

Custom backends (Redis, Memcached, etc.) can be used by implementing the Cache interface:

type Cache interface {
    Get(key string, now time.Time) (Claims, bool)
    Set(key string, claims Claims, now time.Time)
}

Role-based authorization

HasRole checks whether a token carries a specific Keycloak client role:

if !verifier.HasRole(claims, "admin") {
    http.Error(w, "forbidden", http.StatusForbidden)
    return
}

Error handling

All errors wrap one of the package-level sentinels and can be inspected with errors.Is:

switch {
case errors.Is(err, oidcauth.ErrTokenRevoked):
    // token was revoked server-side
case errors.Is(err, oidcauth.ErrTokenValidationFailed):
    // signature / expiry / audience check failed
case errors.Is(err, oidcauth.ErrIntrospectionFailed):
    // could not reach the introspection endpoint
}

Index

Constants

View Source
const DefaultCacheDuration = 5 * time.Minute

DefaultCacheDuration is the TTL used when no custom duration is provided.

Variables

View Source
var (
	ErrInvalidRealmURL       = errors.New("invalid OIDC configuration URL")
	ErrEmptyClientID         = errors.New("client ID cannot be empty")
	ErrTokenValidationFailed = errors.New("token validation failed")
	ErrProviderInitFailed    = errors.New("failed to initialize OIDC provider")
	ErrIntrospectionFailed   = errors.New("token introspection failed")
	ErrTokenRevoked          = errors.New("access token has been revoked")
)

Sentinel errors returned by the OIDC verifier.

Functions

This section is empty.

Types

type Cache

type Cache interface {
	Get(key string, now time.Time) (Claims, bool)
	Set(key string, claims Claims, now time.Time)
}

Cache is the interface for token caching backends. Implement it to plug in Redis, Memcached, or any other store.

type Claims

type Claims struct {
	Aud               []string                       `json:"aud"`
	AllowedOrigins    []string                       `json:"allowed-origins"`
	Jti               string                         `json:"jti"`
	Iss               string                         `json:"iss"`
	Sub               string                         `json:"sub"`
	Typ               string                         `json:"typ"`
	Azp               string                         `json:"azp"`
	Sid               string                         `json:"sid"`
	Acr               string                         `json:"acr"`
	Scope             string                         `json:"scope"`
	Name              string                         `json:"name"`
	PreferredUsername string                         `json:"preferred_username"`
	GivenName         string                         `json:"given_name"`
	FamilyName        string                         `json:"family_name"`
	Email             string                         `json:"email"`
	Exp               float64                        `json:"exp"`
	Iat               float64                        `json:"iat"`
	AuthTime          int                            `json:"auth_time"`
	RealmAccess       map[string][]string            `json:"realm_access"`
	ResourceAccess    map[string]map[string][]string `json:"resource_access"`
	EmailVerified     bool                           `json:"email_verified"`
}

Claims represents the structure of the claims extracted from an authentication token.

type Config

type Config struct {
	RealmURL          string        // Issuer URL (e.g. https://kc.example.com/realms/main).
	ClientID          string        // OAuth client ID used for audience checks and introspection auth.
	ClientSecret      string        // Confidential client secret used for introspection.
	RequestTimeout    time.Duration // HTTP timeout for provider calls. Defaults to 30s.
	SkipIssuerCheck   bool          // Disable iss claim validation (test-only).
	SkipClientIDCheck bool          // Disable aud claim validation against ClientID (test-only).
	SkipExpiryCheck   bool          // Disable exp claim validation (test-only).
}

Config controls how the OIDC verifier connects to the identity provider and validates tokens. RealmURL and ClientID are required; everything else has a sensible default.

type Introspection

type Introspection struct {
	Claims
	Active bool `json:"active"`
}

Introspection represents the result of token introspection, including the claims and the active status of the token.

type MemoryCache

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

MemoryCache is a thread-safe in-memory Cache with TTL-based eviction. Create one with NewMemoryCache and attach it to a verifier via WithCache.

func NewMemoryCache

func NewMemoryCache(ctx context.Context, duration time.Duration) *MemoryCache

NewMemoryCache returns a MemoryCache that caps each entry's TTL at duration and runs a background eviction goroutine until ctx is cancelled or Close is called.

func (*MemoryCache) Close

func (c *MemoryCache) Close()

Close stops the background eviction goroutine. Safe to call multiple times.

func (*MemoryCache) Get

func (c *MemoryCache) Get(key string, now time.Time) (Claims, bool)

Get returns the cached claims for key if present and not yet expired.

func (*MemoryCache) Set

func (c *MemoryCache) Set(key string, claims Claims, now time.Time)

Set stores claims under key, expiring at min(token.exp, now+duration).

type OIDC

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

OIDC verifies tokens issued by an OpenID Connect provider and exposes helpers for role-based authorization. A single OIDC value is safe for concurrent use.

func New

func New(ctx context.Context, config Config, opts ...Option) (*OIDC, error)

New builds an OIDC verifier and discovers the provider's metadata. The supplied context bounds only the discovery call; use WithCache to control caching behaviour.

func (*OIDC) HasRole

func (o *OIDC) HasRole(claims Claims, role string) bool

HasRole reports whether the given claims include the named role for this verifier's client (i.e. claims.ResourceAccess[ClientID].roles).

func (*OIDC) Verify

func (o *OIDC) Verify(ctx context.Context, token string) (Claims, error)

Verify validates a bearer token and returns its claims.

The flow is:

  1. cache lookup, return immediately on hit (if a Cache was attached);
  2. local JWT verification (signature, iss, aud, exp);
  3. remote introspection to catch revoked-but-not-yet-expired tokens;
  4. cache the claims (if a Cache was attached).

Any failure in steps 2 or 3 returns ErrTokenValidationFailed; an inactive token returns ErrTokenRevoked. The original error is wrapped so callers using errors.Unwrap can still inspect it.

type Option

type Option func(*OIDC)

Option configures an OIDC verifier.

func WithCache

func WithCache(c Cache) Option

WithCache attaches a Cache to the verifier. Verify returns cached claims on hit and stores validated claims on miss. Cache lifecycle (e.g. Close) is managed by the caller.

Jump to

Keyboard shortcuts

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