TL;DR
This guide shows how to securely authorize users in an offline application without constant internet connection. We’ll use a token-based system, storing tokens locally and refreshing them when possible.
1. Choose an Authentication Method
Before dealing with offline access, you need a way for users to initially log in. Common options include:
- Username/Password: Simple but requires secure password storage (hashing and salting).
- Social Login: Using providers like Google or Facebook simplifies login but relies on their security.
- Multi-Factor Authentication (MFA): Adds an extra layer of security, highly recommended.
For this guide, we’ll assume you have a working authentication system that returns a JSON Web Token (JWT) upon successful login.
2. Understanding JWTs
JWTs are self-contained tokens containing user information and an expiration time. They’re digitally signed to ensure integrity. A typical JWT looks like this:
Header.Payload.Signature
The Header describes the token type and signing algorithm.
The Payload contains claims about the user (e.g., user ID, roles).
The Signature verifies the token hasn’t been tampered with.
3. Initial Login & Token Storage
- When a user logs in successfully, your server should return a JWT.
- Store this JWT securely on the client-side. Do not store it in LocalStorage due to XSS vulnerabilities. Use:
- HttpOnly Cookies: Best for web applications; inaccessible via JavaScript.
- Secure Storage APIs: Available in native mobile apps (e.g., Keychain on iOS, KeyStore on Android).
- Set an expiration time for the token.
4. Offline Authorization Checks
- Before allowing access to protected resources, check if a valid JWT exists in storage.
- If a token is found:
- Verify the Signature: Use a library appropriate for your programming language (e.g.,
jsonwebtokenin Node.js) to verify the token’s signature against your server’s public key. - Check Expiration Time: Ensure the token hasn’t expired.
- If both checks pass, grant access.
- Verify the Signature: Use a library appropriate for your programming language (e.g.,
- If no valid token is found, redirect the user to the login page.
Example (Node.js with jsonwebtoken):
const jwt = require('jsonwebtoken');
const publicKey = 'YOUR_PUBLIC_KEY'; // Replace with your server's public key
token = localStorage.getItem('token');
jwt.verify(token, publicKey, (err, decoded) => {
if (err) {
// Token is invalid or expired
console.error(err);
// Redirect to login page
} else {
// Token is valid
console.log('User ID:', decoded.userId);
// Grant access
}
});
5. Token Refreshing
Tokens expire for security reasons. Implement a mechanism to refresh them without requiring the user to re-enter their credentials.
- Refresh Tokens: When the user logs in, also issue a long-lived refresh token alongside the JWT. Store this refresh token securely (similar to the JWT).
- Background Refresh: Periodically (e.g., every hour), attempt to use the refresh token to request a new JWT from your server. Do this in the background, without interrupting the user experience.
- Server-Side Validation: The server should validate the refresh token before issuing a new JWT. Consider limiting the number of times a refresh token can be used (to prevent abuse).
Example Refresh Token Flow:
- Client sends refresh token to server.
- Server validates refresh token.
- If valid, server issues new JWT and optionally a new refresh token.
- Client stores the new JWT and refresh token.
6. Security Considerations
- Secure Storage: As mentioned earlier, avoid LocalStorage for sensitive data like tokens.
- HTTPS Only: Always use HTTPS to protect communication between the client and server.
- Token Expiration: Keep token expiration times reasonably short.
- Refresh Token Rotation: Issue a new refresh token with each JWT renewal. This limits the impact of a compromised refresh token.
- Revocation: Implement a mechanism to revoke tokens (e.g., when a user logs out or changes their password).