KAHIBARO
Discord Login Register

13.10. OAuth 2.0

Why OAuth 2.0 Exists

Traditional login systems expect users to create an account and password for every application. This causes several problems:

OAuth 2.0 is an authorization framework that solves this. It lets one application request limited access to a user’s data or actions in another system, without seeing the user’s password.

Example:

Your app never sees the password, only a token with specific, limited permissions.

Key idea: OAuth 2.0 is about authorization, not authentication.
It answers: “Can this app do X on behalf of this user?”
It does not define how to verify identity, even though it is often (incorrectly) used “for login”.

Core Roles in OAuth 2.0

OAuth 2.0 defines four main roles:

RoleAlso calledDescriptionExample
Resource OwnerUserEntity that owns the data or accountThe Google account owner
ClientThird‑party appApplication that wants access on behalf of the userYour calendar integration app
Resource ServerAPI serverServer that hosts the protected resources (APIs)Google Calendar API
Authorization ServerAuth server / IdPServer that issues tokens after user authorizationaccounts.google.com OAuth server

Sometimes the authorization server and resource server are the same application, sometimes not.

Tokens in OAuth 2.0

OAuth 2.0 works with tokens instead of passwords.

Access Tokens

An access token is a credential that represents authorization to access specific resources.

Properties:

http
  GET /user/profile HTTP/1.1
  Host: api.example.com
  Authorization: Bearer ACCESS_TOKEN_HERE

You usually treat access tokens as opaque strings in your backend unless you use something like JWT, which you can decode.

Refresh Tokens

Access tokens expire quickly to limit damage if they leak. Refresh tokens allow the client to obtain new access tokens without re‑prompting the user.

Example token flow:

  1. User signs in and approves the client.
  2. Authorization server returns:
    • access token (short life)
    • refresh token (longer life)
  3. When the access token expires, the client exchanges the refresh token for a fresh access token.

Rule: Never send refresh tokens to the browser or to untrusted client code.
Keep refresh tokens only in secure environments such as server‑side storage.

Scopes

Scopes represent what the client is allowed to do.

Examples:

The client requests scopes during authorization:

http
GET /oauth/authorize?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.com/callback
  &scope=read:user write:tasks
  &state=xyz

The authorization server shows these scopes to the user:

“This app wants to:
- read your profile
- create and edit your tasks”

The user can approve or deny.

Scopes then become part of the access token, and the resource server uses them to allow or deny API calls.

Examples in your own API:

Your backend checks that the access token includes the right scope before performing the operation.

OAuth 2.0 Grant Types

A grant type describes how the client gets an access token.

Modern OAuth 2.0 mostly uses:

The older Implicit grant is considered insecure and should not be used in new systems.

Authorization Code Grant (with PKCE)

Best for:

High level flow:

  1. Client redirects user to authorization server.
  2. User signs in and approves access.
  3. Authorization server redirects back with a short‑lived code.
  4. Client sends this code (plus client credentials and PKCE data) to the authorization server.
  5. Authorization server returns access token (and optionally refresh token).

This keeps client secrets and tokens off the browser when possible.

Example Flow (Authorization Code)

  1. Your app redirects the user:
http
   GET https://auth.example.com/oauth/authorize?
       response_type=code&
       client_id=calendar-app&
       redirect_uri=https://myapp.com/oauth/callback&
       scope=read:calendar&
       state=abc123
  1. User signs in at auth.example.com and approves.
  2. Authorization server redirects back:
http
   GET /oauth/callback?code=AUTH_CODE_HERE&state=abc123 HTTP/1.1
   Host: myapp.com
  1. Your backend exchanges the code:
http
   POST https://auth.example.com/oauth/token
   Content-Type: application/x-www-form-urlencoded
   grant_type=authorization_code&
   code=AUTH_CODE_HERE&
   redirect_uri=https://myapp.com/oauth/callback&
   client_id=calendar-app&
   client_secret=YOUR_CLIENT_SECRET
  1. Authorization server returns:
json
   {
     "access_token": "ACCESS_TOKEN_HERE",
     "token_type": "Bearer",
     "expires_in": 3600,
     "refresh_token": "REFRESH_TOKEN_HERE",
     "scope": "read:calendar"
   }

Your backend then stores the tokens securely, for example in a database or session tied to the user.

PKCE (Proof Key for Code Exchange)

PKCE enhances the Authorization Code flow when you cannot keep a client secret private (for example SPA or mobile app).

Idea:

This prevents attackers that steal the authorization code from being able to exchange it for tokens, because they do not have the code_verifier.

Example:

python
import os, base64, hashlib
# generate code_verifier
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode()
# generate code_challenge
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()

You:

Rule: Use Authorization Code with PKCE for SPAs and native apps.
Use Authorization Code with client secret for traditional server‑side web apps.

Client Credentials Grant

Used when there is no user, only machine to machine communication.

Examples:

Flow:

  1. Client authenticates with its client_id and client_secret.
  2. Authorization server returns an access token that represents the client itself, not a specific user.

Example:

http
POST https://auth.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=service-a&
client_secret=SERVICE_A_SECRET&
scope=orders:read

Response:

json
{
  "access_token": "ACCESS_TOKEN_HERE",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "orders:read"
}

You then send this access token in calls from Service A to Service B.

Important: Client Credentials tokens do not represent a user.
Do not use them when you need user identity or per‑user permissions.

Redirect URIs and Security

Redirect URIs are critical in OAuth 2.0 flows that involve a browser.

Rules:

  1. You must register allowed redirect URIs in the authorization server.
  2. Authorization server must only redirect to allowed URIs.
  3. Do not allow wildcards like https://myapp.com/* if possible.
  4. In development, you can use something like http://localhost:8000/oauth/callback.

Example configuration:

EnvironmentRedirect URI
Devhttp://localhost:8000/oauth/callback
Staginghttps://staging.myapp.com/oauth/callback
Prodhttps://myapp.com/oauth/callback

This prevents open redirect vulnerabilities where an attacker tricks the system into sending tokens to a malicious domain.

State Parameter and CSRF Protection

The state parameter protects against CSRF and some redirect attacks.

Flow:

  1. Before redirecting the user to the authorization server, your app generates a random string and stores it in the session.
  2. It includes that string as state in the authorization URL:
http
   GET https://auth.example.com/oauth/authorize?
       response_type=code&
       client_id=myapp&
       redirect_uri=https://myapp.com/oauth/callback&
       scope=read:user&
       state=RANDOM_STRING
  1. The authorization server returns the same state:
http
   GET /oauth/callback?code=AUTH_CODE&state=RANDOM_STRING
  1. Your app verifies that the state it receives matches the stored value.

If it does not match, it rejects the request.

Rule: Always use state in browser based OAuth flows and always verify it when handling the callback.

Using OAuth 2.0 in Your Own Backend

So far we have talked about OAuth 2.0 as a way to integrate with external providers like Google or GitHub.
You can also use OAuth 2.0 as the foundation of your own authentication and authorization system.

There are two main scenarios:

  1. Your API as a resource server that validates access tokens.
  2. Your backend as an OAuth 2.0 client that calls other APIs using OAuth tokens.

Your API as a Resource Server

In this setup, clients send access tokens with API requests and your backend must:

  1. Validate the token.
  2. Extract scopes and user identity.
  3. Apply authorization rules before processing the request.

Example with a Bearer token:

http
GET /tasks HTTP/1.1
Host: api.example.com
Authorization: Bearer ACCESS_TOKEN_HERE

Your FastAPI backend (pseudocode):

python
from fastapi import Depends, HTTPException, status
from typing import List
class TokenData:
    user_id: int
    scopes: List[str]
def verify_access_token(token: str) -> TokenData:
    # 1. Verify signature / lookup token in DB
    # 2. Check expiration
    # 3. Extract user_id and scopes
    # 4. Raise error if invalid
    ...
def require_scopes(required_scopes: List[str]):
    def dependency(token_data: TokenData = Depends(verify_access_token)):
        if not set(required_scopes).issubset(set(token_data.scopes)):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient scope"
            )
        return token_data
    return dependency
@app.get("/tasks")
def list_tasks(token_data: TokenData = Depends(require_scopes(["tasks:read"]))):
    # You have token_data.user_id and validated scopes
    return get_tasks_for_user(token_data.user_id)

Your token validation might:

Your Backend as an OAuth 2.0 Client

Example: Your backend integrates with GitHub on behalf of users.

High level:

  1. Provide a “Connect GitHub” button that redirects to GitHub’s OAuth authorization URL.
  2. Handle the callback from GitHub.
  3. Exchange the code for tokens.
  4. Store the tokens for that user.
  5. Use the access token when calling GitHub APIs.

Pseudo FastAPI example:

python
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
import httpx
import os
router = APIRouter()
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
GITHUB_REDIRECT_URI = "http://localhost:8000/auth/github/callback"
@router.get("/auth/github")
def github_login():
    url = (
        "https://github.com/login/oauth/authorize"
        f"?client_id={GITHUB_CLIENT_ID}"
        f"&redirect_uri={GITHUB_REDIRECT_URI}"
        "&scope=read:user"
        "&state=random_csrf_token"
    )
    return RedirectResponse(url)
@router.get("/auth/github/callback")
async def github_callback(code: str, state: str, request: Request):
    # TODO check state for CSRF protection
    async with httpx.AsyncClient() as client:
        token_resp = await client.post(
            "https://github.com/login/oauth/access_token",
            headers={"Accept": "application/json"},
            data={
                "client_id": GITHUB_CLIENT_ID,
                "client_secret": GITHUB_CLIENT_SECRET,
                "code": code,
                "redirect_uri": GITHUB_REDIRECT_URI,
            },
        )
        token_data = token_resp.json()
    access_token = token_data["access_token"]
    # Use token to get user info
    async with httpx.AsyncClient() as client:
        user_resp = await client.get(
            "https://api.github.com/user",
            headers={"Authorization": f"Bearer {access_token}"}
        )
        user_data = user_resp.json()
    # Here you would:
    # - find or create a local user linked to user_data["id"]
    # - store access_token (and refresh_token if provided) securely
    # - create a session or JWT for your own app
    ...

This is a typical pattern when you see “Sign in with X” buttons.

Common Pitfalls and Best Practices

Confusing OAuth 2.0 with Authentication

OAuth 2.0 by itself does not tell you how to:

That is what OpenID Connect adds on top of OAuth. It introduces an ID token and standardized user info.

Use:

Storing Tokens Unsafely

Bad practices:

Better practices:

Overbroad Scopes

Do not request more scopes than you need. This:

Design small, focused scopes:

Not Revoking Tokens

Implement ways to:

Summary

In the next related topics, you will connect OAuth 2.0 with OpenID Connect and JWTs to implement full authentication systems for your backend.

Views: 7

Comments

Please login to add a comment.

Don't have an account? Register now!