Identity
A web app, a mobile app and the services they call all need to know who the user is. Several modules each handle one part of that, and picking the wrong one can leave tokens where any script can read them. After this page you can decide which module each of your apps needs, and whether its OAuth application is public or confidential.
Which one do I use?
| You are building | Use |
|---|---|
| A web app with its own server, whose users sign in at an issuer | Auth in server mode, with Sign in with frigear.nu, as a confidential OAuth application |
| A single-page app (SPA) in the browser | Auth in client mode, as a public OAuth application |
| A mobile app | Mobile with Auth in client mode, as a public OAuth application |
| The service the mobile app calls | OAuth Resource |
| Your own issuer | OAuth Server |
| An app that signs in at another issuer (Keycloak, Auth0, Zitadel) | Auth in client mode pointed at it. See Use your own OAuth server |
Both modes have the same app API: useUserSession, useAuth and $api. Both take their guards from
Authorization. In client mode those guards only shape the interface: the API decides.
Terms
- Issuer. The server where users sign in and which issues tokens to other apps. Frigear's issuer is the OAuth Server module.
- Public / confidential. The two OAuth application types, fixed at creation. A confidential one is a server that keeps a client secret, and a public one has no secret.
- PKCE. Proof Key for Code Exchange: a one-time secret that proves the app finishing a sign-in is the app that started it.
- SPA. Single-page application: a browser app with no server of its own.
How it works
The modules are different roles in one OAuth 2.0 / OpenID Connect (OIDC) picture. They are not alternatives for the same job.
The roles
| Role | Module | Runs in | Does |
|---|---|---|---|
| Issuer | OAuth Server | The server of frigear.nu, or your own | Users sign in, OAuth applications are registered, tokens are issued. |
| Server mode app | Auth (Sign in with frigear.nu) | A Nuxt app with a server | Confidential OIDC sign-in at an issuer. The server knows who is signed in. |
| Client mode app | Auth in client mode | The browser or a phone | Signs in with PKCE, keeps and refreshes the tokens, and sends them to the API. |
| Resource server | OAuth Resource | Each service the app calls | Verifies the access token a service receives. |
| App shell | Mobile | The phone | The Capacitor shell. Installs Auth when authentication is configured; the app sets client mode. |
phone: Mobile + Auth (client) ──sign in──▶ issuer: OAuth Server (frigear.nu or your own)
│ │ access token
└──────── Bearer token ────────▶ service: OAuth Resource ──▶ requireAuth
Public and confidential applications
An OAuth application registered at the issuer is one of two types. The type is fixed when the application is created.
- Confidential. A server keeps a secret and sends it with every token request, next to PKCE. Use it for a web app with its own server.
- Public. Anything shipped to a browser or a device can be extracted, so there is no secret. PKCE with S256 protects the code exchange. Refresh token rotation protects the refresh token. Use it for an SPA, a Capacitor app, a desktop app or a command-line tool.
nuxt.config cannot hold a confidential OAuth application. Register one in the admin area of the issuer.PKCE with S256 is mandatory for both types. There is no machine-to-machine flow: every token belongs to a signed-in user.
Sign in with frigear.nu
oauth.frigear in Auth registers GET /auth/frigear, an OIDC sign-in at frigear.nu for pull request previews and local
development. It is decided per request. It is active when either holds:
runtimeConfig.previewDeploymentistrue,runtimeConfig.frigear.ssohas a client id and secret.
Register <origin>/auth/frigear as the callback at the issuer. Users are matched by email. The role claim can be
applied at every sign-in.
One app for every service
frigear.nu can be the only sign-in of one mobile app that talks to every Frigear service, such as vouchers, shop and inventory. The flow:
- The mobile app is a public OAuth application at frigear.nu, with a native redirect URI and no secret.
- It signs in with PKCE. It asks for the scopes of the services it needs, and keeps the rotating refresh token.
- Every access token has one shared audience (
accessTokenAudience). It is short-lived, because services cannot revoke it. - Each service installs OAuth Resource. It verifies the token and hands routes the usual Auth principal, with the
permissionsclaim the issuer froze into the token. Soauthorize(event, 'vouchers:read')works as it does for sessions and API tokens. - frigear.nu lists every service's scopes in
oauthServer.scopes, each with aservicename. The consent screen groups them by it. A scope is granted only to users whose roles allow every permission it stands for, and within the issuer'sroleScopescap.
A token without the scope gets 403 insufficient_scope. An invalid, expired or foreign token gets 401.
Any issuer works
Auth in client mode signs in as a public OAuth application with PKCE at any OIDC issuer that meets a checklist:
- discovery,
- PKCE S256,
- public OAuth applications,
- exact redirect URIs,
- cross-origin resource sharing (CORS) for the webview,
- rotating refresh tokens,
issin the response,- an id_token and a userinfo endpoint.
Point authentication.client.issuer at it, and the rest of the app is unchanged. The checklist and notes per issuer are
in Use your own OAuth server.
Common mistakes
Next steps
- Create an OAuth application – register your app at the issuer.
- Signing keys – generate and rotate the key the issuer signs tokens with.
- Mobile starter – run a mobile app on this stack.
- Access control – see how an access token becomes a principal and how a route decides.