JWT Authentication
JWT Authentication
JSON Web Tokens are the standard for stateless authentication in modern applications.
JWT Structure
A JWT has three parts: header, payload, and signature.
Header.Payload.Signature
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1234567890", "name": "John", "exp": 1717200000}
Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)
Login Flow
// api/auth.js
export const authApi = {
login: async (email, password) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Invalid credentials');
}
const data = await response.json();
// Store tokens securely
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
return data.user;
},
logout: async () => {
const token = localStorage.getItem('refreshToken');
await fetch('/api/auth/logout', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
});
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
},
refreshToken: async () => {
const refreshToken = localStorage.getItem('refreshToken');
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const data = await response.json();
localStorage.setItem('accessToken', data.accessToken);
return data.accessToken;
},
};
Auth Context
// context/AuthContext.jsx
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem('accessToken');
if (token) {
const decoded = decodeToken(token);
if (decoded.exp * 1000 > Date.now()) {
setUser(decoded);
} else {
localStorage.removeItem('accessToken');
}
}
setLoading(false);
}, []);
const login = async (email, password) => {
const userData = await authApi.login(email, password);
setUser(userData);
};
const logout = async () => {
await authApi.logout();
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
Token Refresh Strategy
// utils/tokenRefresh.js
let refreshPromise = null;
export async function getValidToken() {
const token = localStorage.getItem('accessToken');
if (!token) return null;
const decoded = decodeToken(token);
const expiresIn = decoded.exp * 1000 - Date.now();
// Refresh if less than 5 minutes until expiry
if (expiresIn < 5 * 60 * 1000) {
if (!refreshPromise) {
refreshPromise = authApi.refreshToken()
.finally(() => { refreshPromise = null; });
}
return refreshPromise;
}
return token;
}
OAuth Flow
OAuth Flow
OAuth 2.0 enables secure third-party authentication.
Authorization Code Flow
// utils/oauth.js
export function initiateOAuth(provider) {
const params = new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID,
redirect_uri: `${window.location.origin}/auth/callback`,
response_type: 'code',
scope: 'openid email profile',
state: generateRandomState(),
provider,
});
// Store state for verification
localStorage.setItem('oauth_state', params.get('state'));
window.location.href = `/api/auth/${provider}?${params}`;
}
// Handle callback
export async function handleOAuthCallback(code, state) {
const savedState = localStorage.getItem('oauth_state');
if (state !== savedState) {
throw new Error('Invalid OAuth state');
}
localStorage.removeItem('oauth_state');
const response = await fetch('/api/auth/callback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, redirect_uri: window.location.origin }),
});
if (!response.ok) {
throw new Error('OAuth authentication failed');
}
return response.json();
}
Social Login Component
function SocialLogin() {
const { login } = useAuth();
const handleGoogleLogin = async () => {
try {
initiateOAuth('google');
} catch (error) {
showToast('Failed to initiate Google login', 'error');
}
};
const handleGitHubLogin = async () => {
try {
initiateOAuth('github');
} catch (error) {
showToast('Failed to initiate GitHub login', 'error');
}
};
return (
<div className="social-login">
<button onClick={handleGoogleLogin} className="google-btn">
<img src="/google-icon.svg" alt="Google" />
Continue with Google
</button>
<button onClick={handleGitHubLogin} className="github-btn">
<img src="/github-icon.svg" alt="GitHub" />
Continue with GitHub
</button>
</div>
);
}
PKCE Extension
For public clients (SPAs), add PKCE for security:
async function generatePKCE() {
const codeVerifier = generateRandomString(128);
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-')
.replace(///g, '_')
.replace(/=/g, '');
return { codeVerifier, codeChallenge };
}
Session-Based Auth
Session-Based Auth
Session-based authentication uses server-side sessions with session IDs.
How Sessions Work
1. User logs in → Server creates session
2. Server sends session ID in cookie
3. Browser sends cookie with each request
4. Server looks up session data
Implementation
// Login with session
async function login(email, password) {
const response = await fetch('/api/auth/login', {
method: 'POST',
credentials: 'include', // Important for cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) throw new Error('Login failed');
return response.json();
}
// Session check
async function checkSession() {
const response = await fetch('/api/auth/session', {
credentials: 'include',
});
if (response.ok) {
return response.json();
}
return null;
}
// Logout
async function logout() {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
});
}
Session vs JWT
| Feature | Session | JWT |
|---|---|---|
| Storage | Server-side | Client-side |
| Scalability | Harder (shared state) | Easier (stateless) |
| Revocation | Easy (delete session) | Hard (blacklist needed) |
| Size | Small cookie | Larger token |
| Server Load | Higher | Lower |
Best Practices
- Use
HttpOnlycookies for session IDs - Set
SecureandSameSiteflags - Implement session timeout
- Regenerate session ID after login
- Invalidate sessions on logout
// Secure cookie configuration
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
Practice Problems
Create a reusable React component implementing Authentication. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Authentication using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Authentication for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What are the three parts of a JWT?
2. Why is PKCE important for OAuth in SPAs?
3. What is the main advantage of session-based auth over JWT?
4. What flag is required for cookies to work with cross-origin requests?
5. How often should access tokens be refreshed?
Flashcards
Question
What does JWT stand for?
Click to reveal answer
Answer
JSON Web Token - a compact, URL-safe means of representing claims between two parties.
Question
What is the purpose of the OAuth state parameter?
Click to reveal answer
Answer
Prevents CSRF attacks by verifying the authorization response came from the original request.
Question
What cookie flags should be used for session tokens?
Click to reveal answer
Answer
HttpOnly (no JS access), Secure (HTTPS only), SameSite (CSRF protection).
Question
What is PKCE?
Click to reveal answer
Answer
Proof Key for Code Exchange - an OAuth extension that secures authorization code flow for public clients.
Question
Why use refresh tokens with access tokens?
Click to reveal answer
Answer
Access tokens are short-lived for security; refresh tokens allow obtaining new access tokens without re-login.
Revision Notes
Key Takeaways
- 1.JWTs are stateless tokens with header, payload, and signature
- 2.OAuth enables secure third-party authentication
- 3.Session-based auth is easier to revoke but requires server state
- 4.Always use PKCE for OAuth in single-page applications
- 5.Store tokens securely and implement refresh token rotation
Interview Tips
- •Explain the JWT structure and how it's validated
- •Describe the OAuth authorization code flow with PKCE
- •Compare session vs JWT and when to use each
- •Know common security vulnerabilities (token leakage, CSRF)
Cheat Sheet
Authentication Cheat Sheet
JWT Flow
- User logs in → Server creates JWT
- Client stores JWT (localStorage/cookie)
- Client sends JWT in Authorization header
- Server validates JWT signature
OAuth 2.0 Flow
- Client redirects to auth server
- User authenticates
- Auth server redirects with code
- Client exchanges code for tokens
Key Security
- Use HttpOnly cookies for tokens
- Implement token refresh before expiry
- Use PKCE for public clients
- Always validate tokens server-side