Core concepts
This page ties together the terms that keep coming up in Wicket: who issues tokens, to whom, what is issued, on what basis, and who wires all of this together in code.
Issuer
The issuer is the authorization server's identifier. It is written into the iss claim of every token and is the issuer field of the discovery document.
- If
WicketOptions.IssuerUriis set, the issuer is that value (with any trailing/removed). - If it is not set, Wicket derives the issuer from the current request:
http(s)://+r.Host, wherehttpsis chosen solely by whetherr.TLSis non-nil.
Always set IssuerUri explicitly in production. Otherwise the same service reached through different host names yields different issuers, and token validation fails as a result.
opts := godi.GetRequired[*options.WicketOptions](root)
opts.IssuerUri = "https://id.example.com"Clients
A client is an application that requests tokens from Wicket: a backend service, a single-page app, a mobile app, a device, and so on. Clients are described by storage/models.Client. Key fields:
ClientId/ClientSecrets: the client identifier and secrets (secrets are stored hashed withutils.Sha256).- Allowed grant types: set through
SetAllowedGrantTypes(the field itself is unexported, and invalid combinations are rejected). AllowedScopes: the scopes the client may request.RedirectUris: the allowlist of redirect URIs for the authorization code flow.
See Clients and resources for details.
Resources and scopes
Wicket splits "what a token can access" into three models:
| Model | Purpose | Appears in |
|---|---|---|
IdentityResource (identity resource) | A group of user identity claims, such as openid→sub, profile→name, and so on | ID token, UserInfo |
ApiScope (API scope) | A single access permission a client can request, such as orders.read | The access token's scope |
ApiResource (API resource) | A protected API that groups several ApiScopes and holds the ApiSecrets used for introspection | The access token's aud; the caller identity at the introspection endpoint |
When a client requests scope=openid profile orders.read, openid and profile match identity resources, orders.read matches an API scope, and the name of the API resource it belongs to goes into the access token's audience.
Tokens
| Token | Purpose | Format |
|---|---|---|
| Access token | Calling APIs | JWT (header typ is at+jwt), signed with RS256 by default |
| Reference token | An alternative format for access tokens | An opaque handle; the token contents live in ReferenceTokenStore, and APIs must call the introspection endpoint to validate it. Not issued yet in v0.1.4; see the note below |
| ID token | Proves to the client "who logged in" | JWT, issued only for OIDC requests (those that include the openid scope) |
| Refresh token | Exchanged for new access tokens | An opaque handle stored in RefreshTokenStore; the client needs AllowOfflineAccess = true and must request offline_access |
Reference tokens are not wired up in v0.1.4
The Client.AccessTokenType field can be set to wicket.AccessTokenTypeReference, but the v0.1.4 issuance path does not read it: in testing, the token endpoint still returns a JWT after setting it. ReferenceTokenStore is currently used only on the token validation side. If you need access tokens that can be revoked immediately, shorten AccessTokenLifetime.
Grant types
| Grant type | Constant | Typical scenario |
|---|---|---|
client_credentials | wicket.GrantTypesClientCredentials | Service to service, no user involved |
authorization_code | wicket.GrantTypesCode | Web / mobile / SPA with a user login; PKCE required by default |
refresh_token | — | Renewal with a refresh token. It does not need to be listed in the client's allowed grant types; refresh tokens are issued only when the client has AllowOfflineAccess = true and the request includes offline_access |
CIBA (urn:openid:params:grant-type:ciba) | wicket.GrantTypesBackchannelAuthentication | Initiated over the backchannel; the user confirms on another device |
Device code (urn:ietf:params:oauth:grant-type:device_code) | wicket.GrantTypesDeviceFlow | The token endpoint can handle it, but v0.1.4 has no built-in device authorization endpoint |
password | wicket.GrantTypesPassword | Rejected by default; the host must inject a validator with cfg.SetResourceOwnerValidator |
For the full steps of each flow, see Flows.
Authentication schemes
An "authentication scheme" is how the host application recognizes the user who is logged in on the browser side. Wicket predefines two cookie-based scheme names:
| Scheme name | Constant | Purpose |
|---|---|---|
wicket | wicket.DefaultCookieAuthenticationScheme | The main session cookie after the user logs in on the host's login page |
wicket.external | wicket.ExternalCookieAuthenticationScheme | Temporarily holds the external identity during an external login (Google, Facebook, etc.) callback |
v0.1.4 has no built-in cookie handler
By default both schemes are registered with an "unsupported" placeholder handler: authentication always yields "not logged in", and Challenge returns an error. To run flows that require a user login (authorization code, end session, etc.), the host must provide its own auth.Handler implementation for these two schemes:
cfg.SetAuthenticationHandlerProvider(wicket.DefaultCookieAuthenticationScheme,
func(p godi.Provider) auth.Handler { return newMyCookieHandler(p) }) // newMyCookieHandler is implemented by the host (illustrative)Flows that involve no browser user, such as client_credentials, are not affected.
Handlers for the Google, Facebook, and Bearer (JWT Bearer) schemes are also built in; see Identity.
Composition root (di.Root) and ServerConfig
All of Wicket's services are wired by a code-generated dependency injection container. The entry point is di.NewRoot():
root, err := di.NewRoot() // returns (*di.Root, error)
if err != nil { log.Fatal(err) }
defer root.Close()
opts := godi.GetRequired[*options.WicketOptions](root) // protocol options
cfg := root.ServerConfig() // host injection pointThe two objects have clearly separated roles:
*options.WicketOptions: switches and parameters for protocol behavior (issuer, endpoints, interaction page URLs, input length limits, and so on). See Configuration.*di.ServerConfig: where the host injects "the outside world" into Wicket: client and resource lists, signing credentials, the various stores, authentication handlers, the profile service, and so on. Every injection method takes one of two forms:SetXxx(value)orSetXxxProvider(func() value).
Once wiring is complete, call cfg.Freeze(). It checks that every storage port has been injected (or that an in-memory fallback has been declared), and panics immediately otherwise. After that, build the server with server.DefaultServer(root, ...) and get the middleware with srv.UseWicket(). See Embedding in a host application for details.
The three storage port families
Wicket accesses its stateful data through three families of interfaces (ports), and the host injects the implementations:
| Port family | Package | What it stores | Injection methods |
|---|---|---|---|
storage | storage | Authorization codes, refresh tokens, reference tokens, user consents, persisted grants, device codes, CIBA requests, authorization parameter messages | SetAuthorizationCodeStore, SetRefreshTokenStore, etc. |
session | session | Server-side session records | SetSessionStore |
keymgmt | keymgmt | Automatically managed signing keys and their lifecycle | SetRecordStore |
There are also two kinds of input that are not "persistence": clients and resources (SetClients, etc., which are configuration data), and signing credentials and validation keys (SetSigningCredentialStores / SetValidationKeysStores).
Each family has a matching conformance suite (storage/storagetest, session/sessiontest, keymgmt/keymgmttest), and an adapter is only considered compliant once it passes its suite. See Persistence and Storage ports for details.