Skip to content
intermediatePhase 37 · Frontend Architecture

Authentication

Implement JWT, OAuth, and session-based authentication in SPAs.

1h
0 problems
Topic Progress0%

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

  1. Use HttpOnly cookies for session IDs
  2. Set Secure and SameSite flags
  3. Implement session timeout
  4. Regenerate session ID after login
  5. Invalidate sessions on logout
// Secure cookie configuration
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

Practice Problems

0/3solved
Build Authentication Component

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 needed
Authentication Testing

Write 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 tests
Authentication Performance

Optimize 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 analysis

Quiz

1. What are the three parts of a JWT?

Question 1 options

2. Why is PKCE important for OAuth in SPAs?

Question 2 options

3. What is the main advantage of session-based auth over JWT?

Question 3 options

4. What flag is required for cookies to work with cross-origin requests?

Question 4 options

5. How often should access tokens be refreshed?

Question 5 options

Flashcards

Question

What does JWT stand for?

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?

Answer

Prevents CSRF attacks by verifying the authorization response came from the original request.

Question

What cookie flags should be used for session tokens?

Answer

HttpOnly (no JS access), Secure (HTTPS only), SameSite (CSRF protection).

Question

What is PKCE?

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?

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

  1. User logs in → Server creates JWT
  2. Client stores JWT (localStorage/cookie)
  3. Client sends JWT in Authorization header
  4. Server validates JWT signature

OAuth 2.0 Flow

  1. Client redirects to auth server
  2. User authenticates
  3. Auth server redirects with code
  4. 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