Go server quickstart
The Go server SDK verifies auth4.dev access tokens the same way as the TypeScript server SDK. Use it in a net/http API or any Go game backend.
Before you start
You need Go 1.22 or later and three values from the console:
| Option | Console value | Example |
|---|---|---|
TenantID | Project ID | ten_abcdefghijklmnop |
Issuer | Issuer | https://auth.auth4.dev/t/ten_abcdefghijklmnop |
Audience | Audience (the client ID of the application calling your API) | cli_… |
Instead of Issuer you can set AuthOrigin, and the SDK uses{AuthOrigin}/t/{TenantID}:
client, err := auth4.New(auth4.Options{
TenantID: "ten_…",
AuthOrigin: "https://auth.auth4.dev", // the SDK uses https://auth.auth4.dev/t/ten_…
Audience: "cli_…",
})Signing keys always come from {issuer}/.well-known/jwks.json, never from a URL in the token.
1. Add the SDK
The SDK is the Go module in packages/sdk-go. It uses the standard library only. It isn't tagged yet, so add it to your go.mod with areplace directive pointing at a copy of the repository:
require github.com/Plorax/auth4.dev/packages/sdk-go v0.0.0
// Until the module is tagged, point at a copy of the auth4.dev repository.
replace github.com/Plorax/auth4.dev/packages/sdk-go => ../path/to/auth4/packages/sdk-go2. Protect your handlers
Create one client per audience with New, and reuse it so its key cache is shared by every request:
examples/go/gameapi/main.go // One client per audience. Reuse it, so its signing-key cache is shared by every request.
client, err := auth4.New(auth4.Options{
TenantID: os.Getenv("AUTH4_TENANT_ID"),
Issuer: os.Getenv("AUTH4_ISSUER"),
Audience: os.Getenv("AUTH4_AUDIENCE"),
})
if err != nil {
log.Fatal(err)
}Middleware checks the request's Authorization: Bearer token and stores the verified token in the request context. A missing or invalid token gets401 with WWW-Authenticate: Bearer.
examples/go/gameapi/main.go logOutages := auth4.MiddlewareOptions{
// Log the code only: the token never reaches the logs.
OnError: func(_ *http.Request, err *auth4.TokenError) {
if err.IsOutage() {
log.Printf("auth4.dev token check unavailable: %s", err.Code)
}
},
}
signedIn := client.Middleware(logOutages)
mux := http.NewServeMux()
// Any signed-in player, including guests.
mux.Handle("GET /profile", signedIn(http.HandlerFunc(profile)))
// Players with the moderator role only.
mux.Handle("POST /moderation/kick", signedIn(auth4.RequireRole("moderator")(http.HandlerFunc(kick))))In a handler, AccessTokenFromContext() returns the verified token:
examples/go/gameapi/main.gofunc profile(w http.ResponseWriter, r *http.Request) {
token, _ := auth4.AccessTokenFromContext(r.Context())
// token.Subject is the player's user ID. Key your game data on it.
writeJSON(w, map[string]any{
"player": token.Subject,
"guest": token.IsGuest,
"roles": token.Roles,
"level": token.Metadata["level"],
})
}RequireRolelets through players with at least one of the roles, and answers403otherwise.RequireNonGuestlets only upgraded players through, andRequireScoperequires a granted scope.MiddlewareOptionscan let requests without a token through (AllowAnonymous) and report each refused token's error code (OnError).
Run the example from the repository:
cd examples/go
AUTH4_TENANT_ID=ten_… \
AUTH4_ISSUER=https://auth.auth4.dev/t/ten_… \
AUTH4_AUDIENCE=cli_… \
go run ./gameapi
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://127.0.0.1:5080/profileTo catch a wrong project ID or auth origin at startup, call Discover(). It checks that the issuer's discovery document names the same issuer and key URL:
examples/go/gameapi/main.go // Optional: fail fast on a wrong project ID or auth origin.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := client.Discover(ctx); err != nil {
log.Fatal(err)
}Without the middleware
Outside net/http, for example in a game server's own protocol, verify the header or token yourself:
token, err := client.ValidateAuthorizationHeader(ctx, r.Header.Get("Authorization"))
switch {
case auth4.IsOutage(err):
// Signing keys or introspection were unavailable: report an outage, don't sign the player out.
case err != nil:
// Missing, malformed, expired or otherwise invalid: reject the request.
default:
// token.Subject is the player's user ID. Key your game data on it.
}3. Use the claims
| Field | Claim | Meaning |
|---|---|---|
Subject | sub | The player's user ID. It stays the same when a guest upgrades. |
TenantID | tenant_id | Your project ID. Always equal to the configured TenantID. |
ClientID | client_id | The application the token was issued to. |
Scopes | scope | Granted scopes. Check one with HasScope. |
SessionID | sid | The session ID, or empty when the token doesn't belong to a session. |
IsGuest | auth4_guest | true while the player hasn't upgraded their guest account. The next token after an upgrade says false. |
Roles | auth4_roles | The player's roles in this application. Check them with HasRole orHasAnyRole. See Roles and metadata. |
Metadata | auth4_meta | The player's flat metadata. Values are string, float64,bool or nil. Empty when the player has none. |
ExpiresAt | exp | At most five minutes after issue. |
What the SDK checks
- The RS256 signature, with a key from the configured issuer, and the token type
at+jwt. iss,aud,tenant_id,expandnbf, with no clock tolerance by default. Allow up to 60 seconds of drift withClockTolerance.- The claim rules auth4.dev issues tokens with. A token with an unknown or repeated claim, a malformed role or a nested metadata value is rejected.
- ID tokens, tokens from other projects and console management tokens are always rejected.
ID tokens have their own method, ValidateIDToken. Use it only to confirm a sign-in for your own client ID, never to authorize an API call. Pass an empty nonce to skip the nonce check:
idToken, err := client.ValidateIDToken(ctx, rawIDToken, "cli_…", nonce)Failures and key rotation
- Signing keys are cached for five minutes. A token signed with an unknown key ID triggers one key refresh, at most once every 30 seconds, so key rotation needs no restart.
- Errors are
TokenErrorvalues. Only the codeinvalid_tokenmeans the token is bad.jwks_unavailable,jwks_refresh_limitedandintrospection_unavailableare outages, andIsOutage()istrue. - The middleware answers an outage with
503andRetry-After: 5instead of401, so players aren't signed out. SetOutageAsUnauthorizedto answer401instead.
Introspect mode
Introspect mode asks the project's introspection endpoint whether the token's session is still live after offline verification passes. It needs the client ID and secret of a confidential server client of the same project, set through IntrospectionOptions. The example uses a second client for sensitive handlers only, so other requests don't pay for the extra call:
examples/go/gameapi/main.go // Upgraded players only. With server client credentials, a revoked session is refused within
// five seconds instead of when the access token expires.
trade := signedIn(auth4.RequireNonGuest(http.HandlerFunc(tradeItem)))
if id, secret := os.Getenv("AUTH4_SERVER_CLIENT_ID"), os.Getenv("AUTH4_SERVER_CLIENT_SECRET"); id != "" {
sensitive, err := auth4.New(auth4.Options{
TenantID: client.TenantID(),
Issuer: client.Issuer(),
Audience: client.Audience(),
Introspection: &auth4.IntrospectionOptions{
ClientID: id,
ClientSecret: secret,
CacheTTL: 5 * time.Second,
},
})
if err != nil {
log.Fatal(err)
}
trade = sensitive.Middleware(logOutages)(auth4.RequireNonGuest(http.HandlerFunc(tradeItem)))
}
mux.Handle("POST /trade", trade)- The endpoint is derived from the configured issuer, never from the token. It is available as
IntrospectionURL(). - An active answer is reused for
CacheTTL: 5 seconds by default, at most 60. SetDisableCacheto check every request. An inactive answer is kept until the token expires. - If the endpoint can't be reached or answers with an error, verification fails closed with
introspection_unavailable. - The SDK never logs the secret, and leaves it out when the options or the client are printed or encoded as JSON. Load it from your secret store, never from source code.
Create a server client in the console under Applications; see Server clients and their secrets.
SDK notes: packages/sdk-go/README.md. Example: examples/go/README.md. Tests: packages/sdk-go (go test ./...) and tests/integration/auth4d-170.go-sdk.test.ts.