5.0 KiB
go-usertoken guide
How to use the library. Symbol signatures and doc comments are in API. Words for the domain are in CONTEXT.md. Claim layout and the full environment list are in Knowledge platforms/nextcloud/exapps/authentication.md.
ExApp
Purpose
The ExApp mints a short-lived RS256 token for the user AppAPI already named. Only the ExApp holds the private key. Microservices hold the matching public key, or they trust an OIDC issuer instead.
When to use it
Mint a token when the ExApp calls a Microservice on behalf of the requesting user. Do not mint inside a Microservice.
Call sequence
- Generate an RSA key once and keep the private key on the ExApp. Copy
user-token.pubto each Microservice that uses static mode.
openssl genrsa -out user-token.key 2048
openssl rsa -in user-token.key -pubout -out user-token.pub
- Set the ExApp environment:
| Variable | Role |
|---|---|
USER_TOKEN_PRIVATE_KEY_FILE |
RSA private key PEM (PKCS#1 or PKCS#8) |
USER_TOKEN_ISSUER |
iss string |
USER_TOKEN_AUDIENCE |
aud string |
USER_TOKEN_TTL |
optional, default 5m |
- Call
NewSignerFromEnvat startup, orNewSignerwith aSignConfig. - After AppAPI has named the user, call
Signer.Mintwith the current time and aMintInput. - Send
Authorization: Bearerplus the returned string.
MintInput.Subject becomes both sub and preferred_username. Groups nil omits the groups claim. A non-nil pointer includes the claim, including an empty list.
A SignConfig.TTL of zero uses 5 minutes, same as an empty USER_TOKEN_TTL.
Errors
NewSignerFromEnv returns an error when the private-key path is empty, the PEM is not an RSA key, USER_TOKEN_TTL is set and is not a time.Duration, or issuer or audience is empty.
NewSigner returns an error when the private key is nil or issuer or audience is empty.
Mint returns an error when the subject is empty or the key cannot sign. Configuration failures are not ErrUnauthorized. That sentinel is for failed checks on the Microservice side.
Example
signer, err := usertoken.NewSignerFromEnv()
if err != nil {
return err
}
raw, err := signer.Mint(time.Now(), usertoken.MintInput{
Subject: userID,
Groups: &groupIDs, // nil omits groups
})
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+raw)
Microservice
Purpose
A Microservice checks the bearer and stores the caller on the request context. One process uses either a static public key or an OIDC issuer.
When to use it
Wrap HTTP handlers, or install the gRPC interceptor, at process start. Forward the same raw JWT when this service calls another.
Call sequence
- Set exactly one mode.
Static public key (tokens this ExApp minted):
| Variable | Role |
|---|---|
USER_TOKEN_PUBLIC_KEY_FILE |
RSA public key PEM |
USER_TOKEN_ISSUER |
expected iss |
USER_TOKEN_AUDIENCE |
expected aud |
USER_TOKEN_SKEW |
optional, default 1m |
OIDC issuer:
| Variable | Role |
|---|---|
OIDC_ISSUER |
issuer URL that serves discovery |
OIDC_AUDIENCE |
expected aud; empty skips the audience check |
OIDC_GROUPS_CLAIM |
group array claim, default groups |
USER_TOKEN_SKEW |
optional, default 1m |
- Call
FromEnvwith a context. The context is used for OIDC discovery. Or callNewwith aConfigthat sets eitherPublicKeyorOIDCIssuer. - Wrap handlers with
Auth.Middleware. For gRPC, useAuth.UnaryServerInterceptor. Native clients send metadataauthorization. The HTTP gateway's forwarded header isgrpcgateway-authorization. - Inside a handler, read
CallerFromContextandBearerFromContext. - On an outbound call, set
AuthorizationtoBearerplus the raw string fromBearerFromContext.
/health is not authenticated. Any other HTTP path without a valid bearer is 401. A gRPC call without a valid bearer is Unauthenticated.
Caller.Groups is nil when the token omitted the claim, and non-nil (possibly empty) when the claim was present.
Errors
FromEnv and New return an error when both modes are set, when neither mode is set, when the public key file is unreadable, when USER_TOKEN_SKEW cannot be parsed, or when OIDC discovery fails. Those are configuration errors. They do not wrap ErrUnauthorized.
Verify returns an error that wraps ErrUnauthorized when the token is missing, expired, signed by the wrong key, or has the wrong issuer or audience. Middleware and UnaryServerInterceptor turn that into 401 or Unauthenticated and do not call the handler.
An empty OIDCAudience skips the audience check. Static mode always requires an audience.
Example
auth, err := usertoken.FromEnv(ctx)
if err != nil {
return err
}
handler = auth.Middleware()(handler)
// inside a handler
caller, ok := usertoken.CallerFromContext(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
raw, _ := usertoken.BearerFromContext(r.Context())
out.Header.Set("Authorization", "Bearer "+raw)
_ = caller