Concepts

Identity

Which module each of your apps needs to sign people in across a web app, a mobile app and the services they call.

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.

For: app developers who sign users in across more than one app.

Which one do I use?

You are buildingUse
A web app with its own server, whose users sign in at an issuerAuth in server mode, with Sign in with frigear.nu, as a confidential OAuth application
A single-page app (SPA) in the browserAuth in client mode, as a public OAuth application
A mobile appMobile with Auth in client mode, as a public OAuth application
The service the mobile app callsOAuth Resource
Your own issuerOAuth 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
Keep a web app with its own server in Auth's server mode, with its single sign-on. Client mode keeps the tokens in the browser, where any cross-site scripting (XSS) attack can read them. The server also cannot know who is signed in. Moving a web app from server mode to client mode is a downgrade.

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

RoleModuleRuns inDoes
IssuerOAuth ServerThe server of frigear.nu, or your ownUsers sign in, OAuth applications are registered, tokens are issued.
Server mode appAuth (Sign in with frigear.nu)A Nuxt app with a serverConfidential OIDC sign-in at an issuer. The server knows who is signed in.
Client mode appAuth in client modeThe browser or a phoneSigns in with PKCE, keeps and refreshes the tokens, and sends them to the API.
Resource serverOAuth ResourceEach service the app callsVerifies the access token a service receives.
App shellMobileThe phoneThe 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.previewDeployment is true,
  • runtimeConfig.frigear.sso has 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:

  1. The mobile app is a public OAuth application at frigear.nu, with a native redirect URI and no secret.
  2. It signs in with PKCE. It asks for the scopes of the services it needs, and keeps the rotating refresh token.
  3. Every access token has one shared audience (accessTokenAudience). It is short-lived, because services cannot revoke it.
  4. Each service installs OAuth Resource. It verifies the token and hands routes the usual Auth principal, with the permissions claim the issuer froze into the token. So authorize(event, 'vouchers:read') works as it does for sessions and API tokens.
  5. frigear.nu lists every service's scopes in oauthServer.scopes, each with a service name. 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's roleScopes cap.

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,
  • iss in 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

Putting a web app in client mode. Its tokens move into the browser, and its server no longer knows who is signed in. Keep it in server mode.
Revoking access tokens and expecting services to notice. Services cannot revoke an access token. Keep it short-lived and rely on its expiry.

Next steps

Copyright © 2026