Skip to content

Auth (JWT)

pkg/auth/verifier verifies bearer JWTs against one or more trusted issuers and provides a *verifier.Registry into DI. The pkg/auth/fiber and pkg/auth/grpc adapters turn that registry into guard middleware that stashes a verified *verifier.Principal in the request context.

Add verifier.NewModule() to the runtime. It provides *verifier.Registry at modules.auth.verifier.<instance>; each configured issuer’s JWKS is fetched (or discovered via OIDC) and refreshed in the background.

lakta.NewRuntime(
config.NewModule(config.WithConfigDirs(".", "./config"), config.WithArgs(os.Args[1:])),
tint.NewModule(),
slog.NewModule(),
verifier.NewModule(),
fiberserver.NewModule(fiberserver.WithRouterCtx(registerRoutes)),
)
modules:
auth:
verifier:
default:
roles_claim: "roles" # scope_claim defaults to "scope"
issuers:
- issuer: "https://accounts.example.com"
audience: ["my-api"]
algorithms: ["RS256"]
# jwks_url omitted -> discovered from the issuer's OIDC document
- issuer: "https://auth.internal"
audience: ["my-api"]
jwks_url: "https://auth.internal/.well-known/jwks.json"
algorithms: ["ES256"]
clock_skew: "30s"

Each issuer’s audience and algorithms must be non-empty; alg:none is always rejected and clock_skew is capped at 5 minutes.

static_key is an isolated HS256 path for local development. It only loads when LAKTA_PROFILE is set to one of its profiles (default dev, local, test); otherwise Init fails safe rather than trusting the secret. Never enable it in production.

# under modules.auth.verifier.default:
static_key:
secret: "dev-only-secret"
profiles: ["dev", "local"]

WithRouterCtx gives the router the runtime context, so you can Invoke the registry and build the guard middleware. authfiber.New verifies the bearer token; compose RequireScope, RequireRole, RequireAny, or RequireAll after it. Use Optional to allow anonymous requests through.

func registerRoutes(ctx context.Context, app *fiber.App) {
reg, err := lakta.Invoke[*verifier.Registry](ctx)
if err != nil {
panic(err)
}
authn, err := authfiber.New(reg, config.DefaultInstanceName) // errors only if reg is nil
if err != nil {
panic(err)
}
items := app.Group("/items", authn)
items.Get("/", listItems)
items.Post("/", createItem, authfiber.RequireScope("items:write"))
}

Build an interceptor with authgrpc.NewUnaryServerInterceptor and pass it via grpcserver.WithUnaryInterceptor. Wrap it in selector.UnaryServerInterceptor to skip anonymous methods. Enforce fine-grained access inside handlers with authgrpc.RequireScope / authgrpc.RequireRole.

// reg is the *verifier.Registry resolved from DI, as in the fiber example.
interceptor, err := authgrpc.NewUnaryServerInterceptor(reg, config.DefaultInstanceName)
if err != nil {
return err
}
grpcserver.NewModule(
grpcserver.WithService(&v1.MyService_ServiceDesc, NewServer()),
grpcserver.WithUnaryInterceptor(interceptor),
)
func (s *MyServer) DeleteThing(ctx context.Context, req *pb.DeleteThingRequest) (*pb.DeleteThingResponse, error) {
if err := authgrpc.RequireScope(ctx, "things:delete"); err != nil {
return nil, err
}
// ...
}

After a token verifies, the adapters stash a *verifier.Principal. Retrieve it with verifier.PrincipalFrom(ctx) — the same call works from fiber (c.Context()) and gRPC handlers.

p, ok := verifier.PrincipalFrom(c.Context())
if !ok {
// anonymous request (only reachable behind Optional)
}
// p.Subject, p.Issuer, p.Audience, p.Scopes, p.Roles, p.Claims (raw map), p.Token
if p.HasRole("admin") {
// ...
}

Config path: modules.auth.verifier.<name>

issuers
issuers.issuerstring

issuer is matched against the token iss exactly (no prefix/substring)

env
issuers.audience[]string

audience MUST be non-empty; the token aud must intersect it

env
issuers.jwks_urlstring

JWKSURL is the JWKS endpoint; empty triggers OIDC discovery from Issuer

env
issuers.algorithms[]string

algorithms is a hard allowlist, e.g. [RS256, ES256]; alg:none is rejected

env
issuers.clock_skewtime.Duration

clockSkew is capped at maxClockSkew

env
static_key
static_key.algorithmstring
envLAKTA_MODULES__AUTH__VERIFIER__<NAME>__STATIC_KEY__ALGORITHM
static_key.secretstring
envLAKTA_MODULES__AUTH__VERIFIER__<NAME>__STATIC_KEY__SECRET
static_key.profiles[]string
envLAKTA_MODULES__AUTH__VERIFIER__<NAME>__STATIC_KEY__PROFILES
scope_claimstringdefault: scope
envLAKTA_MODULES__AUTH__VERIFIER__<NAME>__SCOPE_CLAIM
roles_claimstring
envLAKTA_MODULES__AUTH__VERIFIER__<NAME>__ROLES_CLAIM