Blog | G5 Cyber Security

OAuth2 Client & Resource Server Authorization

TL;DR

This guide shows how to authorize an OAuth 2.0 application that acts as both a client (requesting access) and a resource server (protecting data). We’ll focus on using JWTs for authorization, covering token exchange, validation, and securing your API endpoints.

1. Understanding the Roles

Before we start, let’s clarify what each role does in this scenario:

Because your app is both, it needs to handle these responsibilities separately.

2. Token Exchange (Client Initiated)

The typical flow starts with a user authenticating. Once authenticated, you’ll exchange the user’s credentials for an access token. This often involves using the Authorization Code grant type or Client Credentials grant type depending on your use case.

2.1 Authorization Code Grant (User Context)

  1. Redirect the user to the authorization server’s login page.
  2. The user logs in and grants consent.
  3. The authorization server redirects back to your application with an authorization code.
  4. Your application exchanges the authorization code for an access token, refresh token (optional), and ID token.

Example request (simplified):

POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code={AUTHORIZATION_CODE}&redirect_uri={REDIRECT_URI}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}

2.2 Client Credentials Grant (Application Context)

  1. Your application authenticates directly with the authorization server using its client ID and secret.
  2. The authorization server issues an access token.

Example request (simplified):

POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}

3. JWT Validation (Resource Server)

When a client attempts to access a protected endpoint, your resource server needs to validate the access token.

3.1 Token Format

We’ll assume you’re using JSON Web Tokens (JWTs). The JWT will contain claims about the user and permissions granted.

3.2 Validation Steps

  1. Verify Signature: Ensure the token hasn’t been tampered with by verifying its signature against the authorization server’s public key.
  2. Check Issuer (iss): Confirm the token was issued by a trusted authorization server.
  3. Validate Audience (aud): Verify that your application is the intended recipient of the token.
  4. Expiration Time (exp): Ensure the token hasn’t expired.
  5. Revocation: Check if the token has been revoked (optional, but recommended). This often involves querying a revocation list or using an introspection endpoint.

Example validation logic (pseudocode):

function validateToken(token) {
  try {
    // Decode and verify the JWT signature
    const decoded = jwt.decodeAndVerify(token, publicKey);

    // Check claims
    if (decoded.iss !== 'https://your-authorization-server') return false;
    if (decoded.aud !== '{YOUR_CLIENT_ID}') return false;
    if (Date.now() > decoded.exp * 1000) return false; // Convert seconds to milliseconds

    return true;
  } catch (error) {
    return false;
  }
}

4. Securing API Endpoints

Protect your API endpoints by intercepting requests and validating the access token before processing them.

  1. Middleware: Implement middleware that extracts the access token from the request (e.g., Authorization header).
  2. Validation Call: Call the validateToken function to verify the token’s authenticity and validity.
  3. Access Control: Based on the claims within the validated JWT, determine if the user has permission to access the requested resource.
  4. Error Handling: Return an appropriate error response (e.g., 401 Unauthorized) if the token is invalid or missing.

Example middleware snippet (pseudocode):

function authenticate(req, res, next) {
  const authHeader = req.headers['authorization'];
  if (!authHeader) return res.status(401).send('No token provided.');

  const token = authHeader.split(' ')[1]; // Assuming 'Bearer {token}' format

  if (validateToken(token)) {
    // Token is valid, attach user information to the request
    req.user = jwt.decode(token);
    next();
  } else {
    return res.status(401).send('Invalid token.');
  }
}

5. Considerations

Exit mobile version