Skip to content

Authorize verified requests

Authentication tells you who the caller is; authorization decides what they may do. authn keeps these separate: once a Verifier returns an Identity, an AuthorizeFunc predicate makes the allow/deny call. The package ships no policy engine — you compose the built-in combinators or supply your own function.

The predicate

An AuthorizeFunc receives the verified identity (and, via the context, the request metadata) and returns true to allow or false to deny:

var authorize authn.AuthorizeFunc = func(ctx context.Context, id *authn.Identity) bool {
    return id.Subject == "admin" // true = allow, false = deny
}

The transport maps a false return to a generic 403 / PermissionDenied — the predicate makes the decision; it does not shape the client-facing response.

Built-in combinators

Require scopes

RequireScopes allows the request only if the identity carries all the named scopes (parsed from the token's scope/scp claim into Identity.Scopes):

authorize := authn.RequireScopes("deploy", "read:logs")

Require a claim

RequireClaim allows the request only if a verified JWT claim equals an expected value:

authorize := authn.RequireClaim("groups", "platform-team")

Both return an AuthorizeFunc, so they drop straight into the same call site as a hand-written predicate.

Using request metadata

An AuthorizeFunc can also see what is being accessed via RequestMetadata (the HTTP method + path, or grpc + the full method name). Your transport puts it on the context; the predicate reads it:

ctx = authn.ContextWithRequestMetadata(ctx, authn.RequestMetadata{Method: "POST", Path: "/deploy"})

var authorize authn.AuthorizeFunc = func(ctx context.Context, id *authn.Identity) bool {
    if md, ok := authn.RequestMetadataFromContext(ctx); ok && md.Method == "POST" {
        // stricter rule for mutating requests
        return authn.RequireScopes("deploy")(ctx, id)
    }
    return true
}

This keeps route-aware authorization in one predicate without teaching authn about your routing table.

Putting it together

The end-to-end shape inside a middleware/interceptor is:

  1. Extract the credential from the transport.
  2. Verify it → Identity (else 401).
  3. authorize(ctx, id) → allow or deny (else 403).
  4. ContextWithIdentity(ctx, id) and call the next handler.

Steps 2 and 3 are the whole of authn; steps 1 and 4 are transport glue you own.