Skip to main content
GET
/
reauthenticate
curl -X GET "https://auth-api.yourdomain.com/reauthenticate" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json"
{
  "message": "Reauthentication successful",
  "timestamp": "2023-12-01T10:30:00Z",
  "valid_until": "2023-12-01T10:45:00Z"
}

Overview

The /reauthenticate endpoint verifies the user’s identity for sensitive operations that require additional security confirmation. This endpoint is typically used before allowing users to perform critical actions like changing passwords, updating payment methods, or accessing sensitive data.
This endpoint requires a valid JWT token in the Authorization header.

Request

curl -X GET "https://auth-api.yourdomain.com/reauthenticate" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json"

Response

{
  "message": "Reauthentication successful",
  "timestamp": "2023-12-01T10:30:00Z",
  "valid_until": "2023-12-01T10:45:00Z"
}

Response Fields

FieldTypeDescription
messagestringSuccess message
timestampstringCurrent timestamp
valid_untilstringWhen the reauthentication expires (typically 15 minutes)

Implementation Examples

React Hook for Reauthentication

import { useState } from 'react';
import { useAuth } from './useAuth';

export function useReauthentication() {
  const [isReauthenticated, setIsReauthenticated] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const { accessToken } = useAuth();

  const reauthenticate = async () => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/auth/reauthenticate', {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        }
      });

      if (!response.ok) {
        throw new Error('Reauthentication failed');
      }

      const result = await response.json();
      setIsReauthenticated(true);
      
      // Auto-expire reauthentication after 15 minutes
      setTimeout(() => {
        setIsReauthenticated(false);
      }, 15 * 60 * 1000);

      return result;
    } catch (err) {
      setError(err.message);
      setIsReauthenticated(false);
      throw err;
    } finally {
      setLoading(false);
    }
  };

  const clearReauthentication = () => {
    setIsReauthenticated(false);
    setError(null);
  };

  return {
    isReauthenticated,
    reauthenticate,
    clearReauthentication,
    loading,
    error
  };
}

// Usage in component
function SensitiveOperation() {
  const { isReauthenticated, reauthenticate, loading } = useReauthentication();
  const [showReauthPrompt, setShowReauthPrompt] = useState(false);

  const handleSensitiveAction = async () => {
    if (!isReauthenticated) {
      setShowReauthPrompt(true);
      return;
    }

    // Proceed with sensitive operation
    await performSensitiveOperation();
  };

  const handleReauthenticate = async () => {
    try {
      await reauthenticate();
      setShowReauthPrompt(false);
      // Now proceed with the sensitive operation
      await performSensitiveOperation();
    } catch (error) {
      console.error('Reauthentication failed:', error);
    }
  };

  if (showReauthPrompt) {
    return (
      <div className="reauthentication-prompt">
        <h3>Security Verification Required</h3>
        <p>This action requires additional verification for your security.</p>
        <button 
          onClick={handleReauthenticate}
          disabled={loading}
        >
          {loading ? 'Verifying...' : 'Verify Identity'}
        </button>
        <button onClick={() => setShowReauthPrompt(false)}>
          Cancel
        </button>
      </div>
    );
  }

  return (
    <button onClick={handleSensitiveAction}>
      Perform Sensitive Action
    </button>
  );
}

Protected Route Component

import { useEffect, useState } from 'react';
import { useReauthentication } from './useReauthentication';

function ProtectedSensitiveRoute({ children }) {
  const { isReauthenticated, reauthenticate, loading } = useReauthentication();
  const [showReauthForm, setShowReauthForm] = useState(false);

  useEffect(() => {
    // Check if reauthentication is needed when component mounts
    if (!isReauthenticated) {
      setShowReauthForm(true);
    }
  }, [isReauthenticated]);

  const handleReauthenticate = async () => {
    try {
      await reauthenticate();
      setShowReauthForm(false);
    } catch (error) {
      console.error('Reauthentication failed:', error);
    }
  };

  if (showReauthForm) {
    return (
      <div className="reauthentication-gate">
        <div className="reauthentication-card">
          <h2>Security Verification</h2>
          <p>
            For your security, please verify your identity to access this section.
          </p>
          <button 
            onClick={handleReauthenticate}
            disabled={loading}
            className="btn-primary"
          >
            {loading ? 'Verifying...' : 'Verify Identity'}
          </button>
        </div>
      </div>
    );
  }

  return children;
}

// Usage
function App() {
  return (
    <Routes>
      <Route path="/settings" element={
        <ProtectedSensitiveRoute>
          <UserSettings />
        </ProtectedSensitiveRoute>
      } />
      <Route path="/billing" element={
        <ProtectedSensitiveRoute>
          <BillingSettings />
        </ProtectedSensitiveRoute>
      } />
    </Routes>
  );
}

Node.js Middleware

const jwt = require('jsonwebtoken');

// Middleware to check if reauthentication is required
function requireReauthentication(maxAge = 15 * 60 * 1000) { // 15 minutes default
  return async (req, res, next) => {
    try {
      const token = req.headers.authorization?.replace('Bearer ', '');
      if (!token) {
        return res.status(401).json({
          code: 401,
          msg: 'No token provided'
        });
      }

      // Decode token without verification to check timestamp
      const decoded = jwt.decode(token);
      if (!decoded) {
        return res.status(401).json({
          code: 401,
          msg: 'Invalid token format'
        });
      }

      const tokenAge = Date.now() - (decoded.iat * 1000);
      
      if (tokenAge > maxAge) {
        return res.status(403).json({
          code: 403,
          msg: 'Reauthentication required',
          details: 'Token is too old for sensitive operations'
        });
      }

      // Check if user has recently reauthenticated
      const reauthKey = `reauth:${decoded.sub}`;
      const reauthTimestamp = await redis.get(reauthKey);
      
      if (!reauthTimestamp || (Date.now() - parseInt(reauthTimestamp)) > maxAge) {
        return res.status(403).json({
          code: 403,
          msg: 'Reauthentication required',
          details: 'Recent identity verification required'
        });
      }

      next();
    } catch (error) {
      res.status(500).json({
        code: 500,
        msg: 'Internal server error'
      });
    }
  };
}

// Reauthentication endpoint handler
app.get('/reauthenticate', authenticateToken, async (req, res) => {
  try {
    const userId = req.user.sub;
    const now = Date.now();
    
    // Store reauthentication timestamp
    const reauthKey = `reauth:${userId}`;
    await redis.setex(reauthKey, 15 * 60, now.toString()); // 15 minutes TTL
    
    res.json({
      message: 'Reauthentication successful',
      timestamp: new Date(now).toISOString(),
      valid_until: new Date(now + 15 * 60 * 1000).toISOString()
    });
  } catch (error) {
    res.status(500).json({
      code: 500,
      msg: 'Internal server error'
    });
  }
});

// Usage on sensitive routes
app.put('/user/password', 
  authenticateToken, 
  requireReauthentication(10 * 60 * 1000), // 10 minutes for password change
  async (req, res) => {
    // Handle password change
    res.json({ message: 'Password updated successfully' });
  }
);

app.delete('/user/account', 
  authenticateToken, 
  requireReauthentication(5 * 60 * 1000), // 5 minutes for account deletion
  async (req, res) => {
    // Handle account deletion
    res.json({ message: 'Account deleted successfully' });
  }
);

Use Cases

Sensitive Operations

Require reauthentication before:
  • Changing passwords or email addresses
  • Updating payment methods
  • Deleting accounts or data
  • Accessing financial information
  • Modifying security settings

Time-Based Security

  • Short-lived verification: 5-15 minutes for critical operations
  • Session-based: Require reauthentication once per session for sensitive areas
  • Operation-specific: Different timeouts for different sensitivity levels

Progressive Security

  • Low sensitivity: No reauthentication required
  • Medium sensitivity: Reauthentication within last 15 minutes
  • High sensitivity: Reauthentication within last 5 minutes
  • Critical operations: Fresh reauthentication required

Security Considerations

  • Token Age: Check both JWT iat (issued at) and recent reauthentication timestamp
  • Secure Storage: Store reauthentication state securely (Redis, encrypted cookies)
  • Time Limits: Use appropriate time limits based on operation sensitivity
  • User Experience: Balance security with usability
  • Audit Logging: Log reauthentication attempts and sensitive operations

Rate Limiting

  • Endpoint: 10 requests per 5 minutes per user
  • Purpose: Prevent abuse while allowing legitimate use
  • Headers: Standard rate limiting headers included in response
  • User Profile - May require reauthentication for sensitive data
  • Update Profile - Requires reauthentication for security fields
  • Logout - Alternative to reauthentication for security

Testing

// Jest test example
describe('Reauthentication', () => {
  test('should succeed with valid token', async () => {
    const response = await request(app)
      .get('/reauthenticate')
      .set('Authorization', `Bearer ${validToken}`);
    
    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('message');
    expect(response.body).toHaveProperty('valid_until');
  });
  
  test('should fail without token', async () => {
    const response = await request(app)
      .get('/reauthenticate');
    
    expect(response.status).toBe(401);
    expect(response.body.msg).toContain('token');
  });
  
  test('should require reauthentication for old tokens', async () => {
    const oldToken = generateToken({ iat: Math.floor(Date.now() / 1000) - 3600 }); // 1 hour old
    
    const response = await request(app)
      .put('/user/password')
      .set('Authorization', `Bearer ${oldToken}`)
      .send({ password: 'newpassword123' });
    
    expect(response.status).toBe(403);
    expect(response.body.msg).toContain('Reauthentication required');
  });
});
I