Understanding OAuth 2.0:
A Developer's Guide (with Example Flow)

Last updated: April 27, 2025

1. Introduction: What Problem Does OAuth 2.0 Solve?

Imagine you want a third-party ("GameTracker") to access your game library stored on another service ("Steam") to print them. You wouldn't want to give GameTracker your Steam username and password, as that grants them full access to everything! This is the problem OAuth 2.0 solves.

OAuth 2.0 is an industry-standard authorization framework, not an authentication protocol. It enables a third-party application (like GameTracker) to obtain limited, delegated access to a user's resources on another service (like Steam), without exposing the user's credentials. Think of it like giving a valet a key that only starts the car and opens the driver door, but doesn't open the trunk or glove compartment – it grants specific, limited permissions.

2. Core Concepts and Roles

Understanding OAuth 2.0 involves knowing the key players:

  • Resource Owner: The user who owns the data/resources and can grant access. (You, in the game library example)
  • Client Application: The third-party application requesting access to the Resource Owner's resources. (GameTracker app)
  • Authorization Server: The server that authenticates the Resource Owner, receives their consent, and issues Access Tokens to the Client Application. (Steam' authorization service)
  • Resource Server: The server hosting the protected resources (e.g., APIs, user data) that accepts and validates Access Tokens from the Client Application. (Steam' API server hosting your games)

Often, the Authorization Server and Resource Server are part of the same logical service (like Steam in the example).

3. OAuth 2.0 Grant Types (Flows)

OAuth 2.0 defines several ways (called "grant types" or "flows") for a Client Application to obtain an Access Token. The appropriate flow depends on the type of application (web server app, single-page app (SPA), native mobile app, machine-to-machine service) and the level of trust.

3.1 Authorization Code Grant (with PKCE)

This is the most common and recommended flow for web applications, native apps, and SPAs where a user is involved. It's considered the most secure standard flow.

High-level steps:

  1. The Client Application redirects the Resource Owner (user) to the Authorization Server's /authorize endpoint, specifying the requested permissions (scopes), its client ID, a redirect URI, and importantly for modern security, PKCE parameters (code_challenge, code_challenge_method).
  2. The Authorization Server authenticates the Resource Owner (e.g., via login form) and prompts them to grant or deny the Client's requested permissions.
  3. If the user grants permission, the Authorization Server redirects the user back to the Client Application's pre-registered redirect_uri with a temporary, single-use Authorization Code.
  4. The Client Application (typically its backend server) exchanges this Authorization Code, along with its client credentials (client ID and secret, if applicable) and the PKCE code_verifier, directly with the Authorization Server's /token endpoint.
  5. The Authorization Server verifies the code, client credentials, and the PKCE verifier. If valid, it issues an Access Token (and usually a Refresh Token) back to the Client Application.

PKCE (Proof Key for Code Exchange) is crucial for public clients (like SPAs and native apps) that cannot securely store a client secret. It involves the client creating a secret (code_verifier), sending a transformed version (code_challenge) in the initial request, and proving possession of the original secret when exchanging the code. This prevents attackers who might intercept the authorization code from exchanging it for a token.

3.1.1 Example Flow

Let's illustrate with simplified URLs and parameters:

  1. Client Initiates Request: The client app generates a random state value and a PKCE code_verifier. It derives the code_challenge (e.g., SHA256 hash of the verifier, Base64URL encoded). It redirects the user's browser to the Authorization Server:
    
    https://auth.example.com/authorize?
    response_type=code&
    client_id=YOUR_CLIENT_ID&
    redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback&
    scope=read_profile%20read_game&
    state=xyzABC123&
    code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
    code_challenge_method=S256
                    
  2. User Authentication & Consent: The user logs in at auth.example.com and sees a screen asking if "GameTracker" (identified by YOUR_CLIENT_ID) can access their profile and game library (defined by scope). The user clicks "Allow".
  3. Redirect Back to Client: The Authorization Server redirects the browser back to the client's specified redirect_uri, appending the authorization code and the original state:
    
    https://client.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=xyzABC123
                     
    (The client MUST verify the received state matches the one it sent in step 1).
  4. Client Exchanges Code for Token: The client's backend server makes a direct POST request to the Authorization Server's token endpoint:
    
    POST /token HTTP/1.1
    Host: auth.example.com
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code&
    code=SplxlOBeZQQYbYS6WxSbIA&
    redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback&
    client_id=YOUR_CLIENT_ID&
    client_secret=YOUR_CLIENT_SECRET&  <-- Only for confidential clients
    code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk <-- The original PKCE secret
                     
  5. Authorization Server Responds with Tokens: If everything is valid, the Authorization Server responds with the tokens:
    
    {
      "access_token": "2YotnFZFEjr1zCsicMWpAA",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
    }
                    
  6. Client Accesses Resource: The client can now use the access_token to make requests to the Resource Server:
    
    GET /games HTTP/1.1
    Host: api.example.com
    Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
                     

3.2 Client Credentials Grant

This flow is used when the Client Application needs to access its *own* resources or act on its own behalf (not on behalf of a user), typically for machine-to-machine (M2M) communication.

The Client Application authenticates directly with the Authorization Server's /token endpoint using its pre-registered client ID and client secret to obtain an Access Token. No user interaction is involved.

Example Request:


POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64_ENCODED(CLIENT_ID:CLIENT_SECRET)

grant_type=client_credentials&
scope=read_reports
         

The response contains an Access Token (but typically no Refresh Token).

3.3 Discouraged/Legacy Grants

  • Implicit Grant: Previously common for SPAs, it returns the Access Token directly in the redirect URI fragment. It's now generally discouraged due to security risks (token leakage via browser history/referrers) and lack of support for Refresh Tokens. The Authorization Code Grant with PKCE is the recommended replacement.
  • Resource Owner Password Credentials (ROPC) Grant: The Client Application collects the user's username and password directly and sends them to the Authorization Server. This bypasses the primary benefit of OAuth (not sharing credentials) and should only be used for highly trusted first-party applications where redirect-based flows are impossible. It's generally discouraged.

4. Tokens: The Keys to Access

Tokens are central to OAuth 2.0, representing granted permissions.

4.1 Access Token

An Access Token is a credential used by the Client Application to access protected resources on the Resource Server on behalf of the Resource Owner.

  • Purpose: Grants access to specific resources defined by the granted scopes.
  • Format: OAuth 2.0 doesn't mandate a specific format, but JSON Web Tokens (JWT) are commonly used, allowing the token itself to contain information (claims) like expiration time, scopes, and user identifier, which can sometimes be validated locally by the Resource Server.
  • Lifespan: Typically short-lived (minutes to hours) to limit the window of opportunity if compromised.
  • Usage: Sent by the Client in the Authorization HTTP header of requests to the Resource Server, usually as a "Bearer" token (e.g., Authorization: Bearer <access_token>).

4.2 Refresh Token

A Refresh Token is a special credential used by the Client Application to obtain a *new* Access Token from the Authorization Server when the current Access Token expires, without requiring the user to re-authenticate.

  • Purpose: Allows for extended access periods and improved user experience by avoiding frequent re-logins.
  • Lifespan: Typically much longer-lived than Access Tokens (days, weeks, or months).
  • Security: Must be stored securely by the Client Application (e.g., encrypted on a backend server) as they represent long-term access potential. They should never be stored in browser local storage for SPAs.
  • Usage: Sent only to the Authorization Server's /token endpoint (along with client credentials if applicable) to request a new Access Token (and potentially a new Refresh Token - known as refresh token rotation).
  • Issuance: Not issued in all flows (e.g., typically not in Implicit or Client Credentials). Usually obtained alongside the Access Token in the Authorization Code flow.

5. Scopes: Limiting Permissions

Scopes are used to limit the amount of access granted to an Access Token. Instead of granting full access, the Client Application requests specific permissions (scopes) it needs (e.g., read_profile, write_game, read_contacts).

The Resource Owner is typically shown these requested scopes during the consent step and can approve or deny them. The issued Access Token will then be associated with the granted scopes, and the Resource Server will enforce these limitations when the token is used to access resources. Requesting only the necessary scopes adheres to the principle of least privilege.

6. Security Considerations

Implementing OAuth 2.0 securely is crucial:

  • Use HTTPS Everywhere: All communication involving OAuth (redirects, token exchange, API calls) MUST use TLS/HTTPS to protect codes and tokens in transit.
  • Validate Redirect URIs: Authorization Servers must strictly validate the redirect_uri provided in requests against pre-registered URIs to prevent codes/tokens from being sent to malicious sites.
  • Use the `state` Parameter: In redirect-based flows, the Client should generate a unique, unpredictable state value, send it in the authorization request, and verify it matches upon receiving the redirect back from the Authorization Server. This helps prevent Cross-Site Request Forgery (CSRF) attacks.
  • Use PKCE: Always use the Authorization Code grant with PKCE for public clients (SPAs, native apps) to prevent authorization code interception attacks.
  • Secure Client Secrets: Confidential clients (e.g., web server apps) must store their client secrets securely and never expose them.
  • Secure Token Storage: Store Refresh Tokens securely (e.g., encrypted server-side). Avoid storing Access Tokens or Refresh Tokens in browser local storage for SPAs due to XSS risks. Consider secure HTTP-only cookies or in-memory storage.
  • Short-Lived Access Tokens: Keep Access Token lifetimes short to minimize the impact if compromised.
  • Token Revocation: Implement mechanisms to revoke Refresh Tokens (and potentially Access Tokens) if they are compromised or no longer needed (e.g., on user logout).
  • Scope Limitation: Request only the minimum necessary scopes (principle of least privilege).

7. OAuth 2.0 vs. OpenID Connect (OIDC)

It's important to distinguish OAuth 2.0 from OpenID Connect (OIDC). While related, they serve different primary purposes:

  • OAuth 2.0: Focuses on Authorization – granting access to resources. It provides Access Tokens.
  • OpenID Connect (OIDC): Focuses on Authentication – verifying the user's identity. It's built *on top of* OAuth 2.0 and adds an ID Token (a JWT containing user identity information) alongside the OAuth tokens.

If your goal is purely to allow a client to access an API on behalf of a user, OAuth 2.0 is sufficient. If you need to log the user into the client application itself (authentication), you typically use OIDC.

8. Conclusion

OAuth 2.0 is the standard framework for delegated authorization on the web. By understanding its core roles (Resource Owner, Client, Authorization Server, Resource Server), grant types (especially Authorization Code with PKCE and Client Credentials), the purpose of Access and Refresh Tokens, and the importance of scopes and security best practices, developers can securely allow third-party applications to access user resources without sharing sensitive credentials. It's a fundamental protocol for building modern, interconnected applications and APIs.

9. Additional Resources

Related Articles

External Resources