At a Glance

Concern Server Web App Browser SPA Native Mobile
PKCE required?RecommendedMandatoryMandatory
Client secret?Yes (server-side only)NoNo
Token storageHttpOnly cookie / server sessionsessionStorage or BFF cookieKeychain (iOS) / Keystore (Android)
Redirect URI schemehttps://https://Custom scheme or HTTPS Universal Link
In-app browserN/AN/AASWebAuthSession / Chrome Custom Tab
Silent renewalServer-side refresh token callHidden iframe or silent_redirect_uriBackground refresh token grant

Server-Rendered Web Apps (.NET, Rails, Laravel, Django)

Server-rendered apps are confidential clients — they run on a server you control and can securely store a client secret. The auth flow runs entirely server-side: the user is redirected to the identity provider, the server exchanges the authorisation code for tokens at the token endpoint, and a session cookie is set in the browser. Tokens never touch the browser DOM.

Key advantages: The browser never sees a raw token. Tokens live in server memory or an encrypted session store. Even a complete XSS compromise of the front-end HTML cannot exfiltrate tokens. This is the most secure architecture for B2C web apps when server-rendering is an option.

Session management: Set a short absolute timeout (e.g. 8 hours) and a shorter idle timeout (e.g. 30 minutes). Renew the access token server-side using the stored refresh token before it expires — the user never experiences a re-login.

// .NET 10 — server-rendered Razor Pages, confidential client
.AddOpenIdConnect(options => {
    options.Authority     = "https://auth.ailacs.com";
    options.ClientId      = config["Ailacs:ClientId"];
    options.ClientSecret  = config["Ailacs:ClientSecret"]; // safe server-side
    options.ResponseType  = "code";
    options.UsePkce       = true;   // defence-in-depth even for confidential clients
    options.SaveTokens    = true;
    options.UseTokenLifetime = false; // manage session lifetime separately
});

Single-Page Applications (React, Vue, Angular)

SPAs are public clients — all code is delivered to the browser and no secret can be kept confidential. PKCE is therefore mandatory, not optional, for SPAs. There is no client secret; the PKCE code verifier is the only security mechanism preventing code interception attacks.

The BFF pattern is preferred in 2026: Rather than handling tokens in the browser, a Backend-for-Frontend (Next.js API route, Express middleware) performs the OIDC flow server-side and issues the SPA an HttpOnly session cookie. The SPA calls the BFF's API endpoints; it never sees an access or refresh token. This eliminates the XSS token theft attack class entirely.

If you must handle tokens in the browser: Store in sessionStorage (cleared on tab close, not accessible cross-origin), use automaticSilentRenew with a silent_redirect_uri, and implement a strict CSP that prevents unsafe-inline scripts.

Native Mobile Apps (iOS / Android)

Native mobile apps are also public clients — PKCE is mandatory. However, the auth flow has mobile-specific requirements that differ meaningfully from web.

Use the system browser, not a WebView

Apple's ASWebAuthenticationSession (iOS) and Chrome Custom Tabs (Android) are the correct way to show the authorisation endpoint in a mobile app. Never embed an in-app WebView for auth — the app can inspect the WebView's cookies and intercept credentials, which defeats the security model of the authorisation code flow entirely. App Store and Play Store guidelines now prohibit WebView-based OAuth for this reason.

Redirect URIs: custom scheme vs. Universal Links

After authentication the identity provider redirects to your app. Two options: (1) Custom URI scheme (com.yourapp.auth://callback) — simple to configure but can be intercepted by a malicious app that registers the same scheme. (2) HTTPS Universal Links (iOS) / App Links (Android) — associate the redirect URI with your HTTPS domain, cryptographically verified by the OS. HTTPS Universal Links are recommended for high-security B2C apps.

Token storage on mobile

Store tokens in the iOS Keychain or Android Keystore — hardware-backed secure enclaves. Never store tokens in UserDefaults, SharedPreferences, or flat files. Use biometric authentication to gate Keychain/Keystore access for an additional layer of protection on shared or lost devices.

Token Storage Rules — Quick Reference

✓ DO: HttpOnly, Secure, SameSite=Lax cookies (server web apps and BFF pattern)
✓ DO: iOS Keychain / Android Keystore (native mobile)
✓ DO: sessionStorage with CSP (SPAs without BFF, last resort)
✗ NEVER: localStorage (persists across sessions, accessible to XSS)
✗ NEVER: In-app WebView (app can intercept credentials)
✗ NEVER: URL fragment or query string (logged in browser history and server access logs)

Redirect URI Best Practices

Register exact redirect URIs in the Ailacs Identity Portal — no wildcards, no trailing slashes unless intentional. The identity server validates the redirect URI strictly against the registered values. A mismatch returns an error; a wildcard allows open redirect attacks.

  • Web: https://yourapp.com/callback
  • SPA: https://yourapp.com/callback + https://yourapp.com/silent-renew
  • iOS Universal Link: https://yourapp.com/oauth/callback
  • Android App Link: https://yourapp.com/oauth/callback
  • iOS custom scheme (fallback): com.yourapp.auth://callback

Part of the Ultimate Guide to B2C Authentication in 2026

← Back to Main Guide Next: Passwordless B2C →