keycloakauth

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 17 Imported by: 0

README

go-keycloak-auth

Keycloak JWT validation and net/http middleware for Go. Knows the Keycloak claim layout (realm and client roles), handles JWKS key rotation safely, and nothing else — it protects APIs, it does not manage Keycloak.

Go Reference CI

Why

gocloak is a full admin-API client; coreos/go-oidc is generic OIDC that knows nothing about realm_access / resource_access. The common production need sits in between: "validate the realm's tokens in front of my API and give me the roles". That's the whole scope of this package.

  • Signature (RS/ES only — none and HS* are rejected), issuer, expiry/nbf with leeway, optional audience.
  • Rejects tokens whose typ is not Bearer, so a valid-signature ID token can't pass API auth.
  • JWKS rotation: an unknown kid triggers a refetch, rate-limited and deduplicated (singleflight) — a flood of bogus tokens can't hammer Keycloak.
  • Typed errors matched with errors.Is.

Install

go get github.com/YusufDrymz/go-keycloak-auth

Usage

v, err := keycloakauth.New(ctx, keycloakauth.Config{
    BaseURL: "https://sso.example.com", // legacy (<17) installs: include /auth
    Realm:   "my-realm",
})
if err != nil {
    log.Fatal(err) // wrong URL/realm fails here, not on the first request
}

mux.Handle("/me", v.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    claims, _ := keycloakauth.FromContext(r.Context())
    fmt.Fprintf(w, "hello %s", claims.PreferredUsername)
})))

mux.Handle("/admin", v.Middleware(
    keycloakauth.RequireRealmRole("admin")(adminHandler),
))

Client roles work the same way: keycloakauth.RequireClientRole("my-api", "reader"), or check by hand with claims.HasClientRole("my-api", "reader").

Outside HTTP (queue consumers, gRPC interceptors), call Verify directly:

claims, err := v.Verify(ctx, rawToken)
switch {
case errors.Is(err, keycloakauth.ErrTokenExpired):
case errors.Is(err, keycloakauth.ErrUnknownKey):
case errors.Is(err, keycloakauth.ErrWrongIssuer):
}
Options
Option Default
WithAudience(aud) off require aud; Keycloak only sets it with an audience mapper
WithLeeway(d) 30s clock-skew tolerance for exp/nbf
WithIssuer(iss) derived expected iss when the public URL differs from BaseURL
WithRefetchInterval(d) 1m min interval between unknown-kid JWKS refetches
WithHTTPClient(c) 10s timeout client used for JWKS fetches
WithErrorHandler(f) 401 + WWW-Authenticate custom middleware error response

Notes

  • The only dependency is golang-jwt/jwt/v5 — signature verification is not something to hand-roll; JWKS handling, caching and the middleware are standard library.
  • One Verifier per realm; construct once, share freely (concurrency-safe).
  • Token issuance (client credentials, refresh) is out of scope for v0.1.

Testing your integration

New and Verify run against any HTTP server, so a fake Keycloak is an httptest.Server serving a JWKS document — sign test tokens with an RSA key generated in the test. See verifier_test.go for a ready-made pattern.

🇹🇷 Türkçe

Keycloak realm'inin token'larını doğrulayan ve net/http handler'larını koruyan küçük paket. Realm/client rollerini (realm_access, resource_access) tanır; JWKS key rotation'ı rate-limit + singleflight ile güvenli yönetir (bozuk token seliyle Keycloak'a fetch fırtınası yaratılamaz).

Kurulum: go get github.com/YusufDrymz/go-keycloak-auth

v, _ := keycloakauth.New(ctx, keycloakauth.Config{
    BaseURL: "https://sso.example.com", Realm: "my-realm",
})
mux.Handle("/admin", v.Middleware(keycloakauth.RequireRealmRole("admin")(handler)))

Hatalar typed: ErrTokenExpired, ErrInvalidSignature, ErrWrongIssuer, ErrUnknownKey... (errors.Is). alg=none ve HS* reddedilir; typ=Bearer olmayan (ID/refresh) token'lar imzası geçerli olsa da geçemez. Token üretme / admin API kapsam dışıdır — bu paket sadece API korur.

License

MIT — see LICENSE.

Documentation

Overview

Package keycloakauth validates Keycloak-issued JWTs and protects net/http handlers. It knows the Keycloak claim layout (realm_access, resource_access) and handles JWKS key rotation with rate-limited, deduplicated refetches.

It deliberately does NOT obtain or refresh tokens and is not an admin API client — it is the small piece you need to put a Keycloak realm in front of an HTTP API, nothing more.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrMissingToken     = errors.New("keycloakauth: missing bearer token")
	ErrMalformedToken   = errors.New("keycloakauth: malformed token")
	ErrInvalidSignature = errors.New("keycloakauth: invalid signature")
	ErrTokenExpired     = errors.New("keycloakauth: token expired")
	ErrTokenNotYetValid = errors.New("keycloakauth: token not valid yet")
	ErrWrongIssuer      = errors.New("keycloakauth: wrong issuer")
	ErrWrongAudience    = errors.New("keycloakauth: wrong audience")
	ErrUnknownKey       = errors.New("keycloakauth: token signed with unknown key")
)

Sentinel errors for matching with errors.Is. Verify wraps them with detail, the middleware maps them to HTTP responses.

Functions

func NewContext

func NewContext(ctx context.Context, c *Claims) context.Context

NewContext returns ctx carrying the claims. The middleware calls this; exported for tests and custom middlewares.

func RequireClientRole

func RequireClientRole(clientID, role string) func(http.Handler) http.Handler

RequireClientRole rejects with 403 unless the client role is present. Must run inside Middleware.

func RequireRealmRole

func RequireRealmRole(role string) func(http.Handler) http.Handler

RequireRealmRole rejects with 403 unless the realm role is present. Must run inside Middleware.

Types

type Claims

type Claims struct {
	Subject           string
	Email             string
	PreferredUsername string
	RealmRoles        []string
	Expiry            time.Time
	Raw               map[string]any
}

Claims is the validated token content. Raw holds every claim as decoded JSON for anything not mapped to a field.

func FromContext

func FromContext(ctx context.Context) (*Claims, bool)

FromContext returns the claims stored by the middleware.

func (*Claims) ClientRoles

func (c *Claims) ClientRoles(clientID string) []string

ClientRoles returns the roles granted for one client (resource_access).

func (*Claims) HasClientRole

func (c *Claims) HasClientRole(clientID, role string) bool

HasClientRole reports whether the client role is present.

func (*Claims) HasRealmRole

func (c *Claims) HasRealmRole(role string) bool

HasRealmRole reports whether the realm role is present.

type Config

type Config struct {
	// BaseURL is the Keycloak root, e.g. "https://sso.example.com".
	// Legacy (<17) installations include the /auth prefix.
	BaseURL string
	Realm   string
}

Config identifies the Keycloak realm to trust.

type Option

type Option func(*Verifier)

Option configures a Verifier.

func WithAudience

func WithAudience(aud string) Option

WithAudience additionally requires the token's aud to contain the value. Off by default because Keycloak does not put the client in aud unless an audience mapper is configured on the client scope.

func WithErrorHandler

func WithErrorHandler(f func(http.ResponseWriter, *http.Request, error)) Option

WithErrorHandler replaces the middleware's 401 response writer.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient injects a custom *http.Client for JWKS fetches.

func WithIssuer

func WithIssuer(iss string) Option

WithIssuer overrides the expected iss claim, for setups where the public issuer URL differs from the URL the service uses to reach Keycloak.

func WithLeeway

func WithLeeway(d time.Duration) Option

WithLeeway sets the clock-skew tolerance for exp/nbf/iat (default 30s).

func WithRefetchInterval

func WithRefetchInterval(d time.Duration) Option

WithRefetchInterval sets the minimum time between JWKS refetches triggered by unknown kids (default 1m). Zero disables the rate limit; concurrent refetches are still deduplicated.

type Verifier

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

Verifier validates tokens issued by one Keycloak realm. It is safe for concurrent use; construct once and share.

func New

func New(ctx context.Context, cfg Config, opts ...Option) (*Verifier, error)

New builds a Verifier and eagerly loads the realm's JWKS, so a wrong BaseURL or realm fails here instead of on the first request.

func (*Verifier) Middleware

func (v *Verifier) Middleware(next http.Handler) http.Handler

Middleware validates the Authorization bearer token and stores the claims in the request context. Failures go through the error handler (default: 401 with a WWW-Authenticate header per RFC 6750).

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, rawToken string) (*Claims, error)

Verify checks signature, issuer, expiry/nbf (with leeway) and — when configured — audience, then returns the mapped claims.

Directories

Path Synopsis
examples
basic command
A minimal API protected by a Keycloak realm: /me needs a valid token, /admin additionally needs the "admin" realm role.
A minimal API protected by a Keycloak realm: /me needs a valid token, /admin additionally needs the "admin" realm role.

Jump to

Keyboard shortcuts

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