# go-usertoken guide How to use the library. Symbol signatures and doc comments are in [API](api.md). Words for the domain are in [CONTEXT.md](../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 1. Generate an RSA key once and keep the private key on the ExApp. Copy `user-token.pub` to each Microservice that uses static mode. ```bash openssl genrsa -out user-token.key 2048 openssl rsa -in user-token.key -pubout -out user-token.pub ``` 2. 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` | 3. Call `NewSignerFromEnv` at startup, or `NewSigner` with a `SignConfig`. 4. After AppAPI has named the user, call `Signer.Mint` with the current time and a `MintInput`. 5. Send `Authorization: Bearer` plus 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 ```go 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 1. 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` | 2. Call `FromEnv` with a context. The context is used for OIDC discovery. Or call `New` with a `Config` that sets either `PublicKey` or `OIDCIssuer`. 3. Wrap handlers with `Auth.Middleware`. For gRPC, use `Auth.UnaryServerInterceptor`. Native clients send metadata `authorization`. The HTTP gateway's forwarded header is `grpcgateway-authorization`. 4. Inside a handler, read `CallerFromContext` and `BearerFromContext`. 5. On an outbound call, set `Authorization` to `Bearer ` plus the raw string from `BearerFromContext`. `/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 ```go 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 ```