Credential management is what keeps PWA users logged in, not just a login form
Persistent identity across installs, browser sessions, and device switches is what makes PWAs feel like installed apps.
Why identity continuity matters for installed web apps
When users install a web app, they expect the app to remember them. They don't want to re-authenticate every time they open it. But PWAs run in browsers, and browsers have their own session management, cookie policies, and security constraints that can interrupt user identity if not handled correctly.
A PWA that drops authentication every few days feels broken. An安装的 web app with seamless login feels native.
The PWA authentication stack
Your identity strategy should layer these components:
User Action
↓
Credential UI (Credential Management API)
↓
Authentication API (WebAuthn / OAuth / password)
↓
Session Token (JWT / cookie)
↓
Service Worker Cache (offline session validation)
↓
Background Sync (token refresh when online)Each layer handles a different use case. If one fails, the layers below can't compensate.
Credential Management API: The native login UI
The Credential Management API replaces the browser's old "save password" dialogs with a programmatic interface you control.
Basic credential storage
// After successful login
navigator.credentials.store({
id: userId,
password: plainPassword, // Only for password auth
name: userDisplayName,
iconURL: avatarUrl
});Federated credentials (Google, Facebook, etc.)
navigator.credentials.store({
id: googleUserId,
type: 'federated',
provider: 'https://accounts.google.com',
name: userDisplayName,
iconURL: avatarUrl
});Public key credentials (WebAuthn / passkeys)
navigator.credentials.store({
id: credentialId,
type: 'public-key',
transports: ['internal', 'hybrid'],
name: userDisplayName,
iconURL: avatarUrl
});Retrieval on app load
navigator.credentials.get({
password: true,
federated: {
providers: ['https://accounts.google.com']
}
}).then(credential => {
if (credential) {
// Autologin or prompt user to confirm
authenticateWithCredential(credential);
}
});Service worker session persistence
Browsers may clear session cookies when users quit the browser. Service workers provide more durable storage for session validation.
Session caching strategy
// service-worker.js
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/auth/validate')) {
event.respondWith(
caches.open('auth-session').then(cache =>
cache.match('current-session').then(cachedResponse => {
if (cachedResponse) {
// Validate cached session with backend
return fetch('/api/auth/validate', {
headers: { 'X-Auth-Token': cachedResponse.headers.get('X-Auth-Token') }
}).then(response => {
if (response.ok) {
cache.put('current-session', response.clone());
return response;
} else {
cache.delete('current-session');
return fetch(event.request);
}
});
}
return fetch(event.request).then(response => {
if (response.ok) {
cache.put('current-session', response.clone());
}
return response;
});
})
)
);
}
});Offline identity handling
When your PWA is offline, cached credentials won't work. Handle this gracefully:
// service-worker.js
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/') && !navigator.onLine) {
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
return response;
}
// Return offline response for auth-dependent routes
return new Response(JSON.stringify({
error: 'offline',
message: 'Please connect to the internet to sign in'
}), {
status: 503,
headers: new Headers({ 'Content-Type': 'application/json' })
});
})
);
}
});Token refresh with background sync
Session tokens expire. Background sync silently refreshes them so users don't notice.
// service-worker.js
self.addEventListener('sync', (event) => {
if (event.tag === 'token-refresh') {
event.waitUntil(
fetch('/api/auth/refresh')
.then(response => response.json())
.then(data => {
// Update stored credential with new token
caches.open('auth-session').then(cache =>
cache.put('current-session', new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
}))
);
})
.catch(error => console.error('Token refresh failed:', error))
);
}
});
// Register sync after successful authentication
navigator.serviceWorker.ready.then(registration => {
registration.sync.register('token-refresh');
});WebAuthn integration patterns
WebAuthn (FIDO2) enables passwordless authentication with device-bound credentials. This is gold for PWAs because it's tied to the device, not the browser session.
Registration
const publicKeyCredentialCreationOptions = {
challenge: new Uint8Array(32),
rp: {
name: 'Your App',
id: window.location.hostname
},
user: {
id: new Uint8Array(16),
name: 'user@example.com',
displayName: 'John Doe'
},
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'preferred'
}
};
navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
}).then(credential => {
// Send credential to backend for registration
registerPublicKeyCredential(credential);
});Authentication
const publicKeyCredentialRequestOptions = {
challenge: new Uint8Array(32),
rpId: window.location.hostname,
allowCredentials: [],
userVerification: 'preferred'
};
navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions
}).then(assertion => {
// Send assertion to backend for verification
authenticateWithPublicKeyCredential(assertion);
});Practical implementation checklist
Before shipping authentication, verify:
- [ ] Credential Management API is used for password and federated login
- [ ] WebAuthn is offered as an option (passwordless / passkeys)
- [ ] Service worker caches session tokens for offline validation
- [ ] Background sync handles token refresh silently
- [ ] Clear logout flow clears credentials, caches, and service worker storage
- [ ] Session expiration is communicated to users (not just silent logout)
- [ ] Cross-device identity sync is documented (e.g., password managers, cloud sync)
Identity continuity across installs
When users install your PWA on multiple devices, they expect their identity to follow them.
Scenario 1: User installs PWA on new device
- Browser offers to import credentials from password manager
- Your app recognizes existing user account via email or user ID
- Seamless login without re-authentication
Scenario 2: User switches browsers on same device
- Credentials stored in Credential Management API are browser-scoped
- Offer password export/import or use password manager synchronization
- WebAuthn credentials are device-scoped and don't transfer
Scenario 3: User re-installs PWA
- Service worker cache is cleared on reinstall
- Credential Management API persists (unless user clears site data)
- WebAuthn credentials persist (device-bound)
Common authentication anti-patterns to avoid
Anti-pattern 1: In-memory session tokens
// ❌ Bad: Lost on navigation/refresh
let sessionToken;
function login(creds) {
sessionToken = authenticate(creds);
}// ✅ Good: Stored in service worker cache
function login(creds) {
return authenticate(creds).then(token => {
caches.open('auth-session').then(cache =>
cache.put('current-session', new Response(token))
);
});
}Anti-pattern 2: Silently logout without feedback
// ❌ Bad: User discovers logout when functionality breaks
if (tokenExpired) {
clearCredentials();
}// ✅ Good: Inform user and guide to login
if (tokenExpired) {
showNotification('Session expired. Please sign in again.');
clearCredentials();
navigateToLogin();
}Anti-pattern 3: No offline login guidance
// ❌ Bad: Offline users see cryptic auth errors
fetch('/api/auth/login')
.catch(error => showError('Authentication error'));// ✅ Good: Clear offline messaging
if (!navigator.onLine) {
showNotification('Please connect to the internet to sign in');
showOfflineLoginUI();
}Sources
- Credential Management API
- WebAuthn API
- Service Worker Cache API
- Background Sync API
- OAuth 2.0 for Browser-Based Apps
Next steps
Implement identity continuity in stages:
- Replace traditional login forms with Credential Management API
- Add WebAuthn (passkeys) as an authentication option
- Implement service worker session caching
- Add background sync for token refresh
- Test authentication flows across installs, browsers, and devices
Your PWA should feel as persistent and personalized as a native app. Seamless login is a keytrust signal for installed users.