What is OAuth 2.0#
OAuth 2.0 provides a framework for granting limited access to resources without sharing credentials. It doesn’t confirm the user’s identity by itself. That’s why people often combine OAuth 2.0 with OpenID Connect (OIDC), which adds an identity layer on top of OAuth 2.0 for authentication.
It’s not an authentication protocol by itself (though often used with OpenID Connect for authentication).
It defines how a client application can request and obtain an access token from an authorization server, which can then be used to access protected resources on behalf of the user.
OAuth2 is specifically designed for authorization. It defines how a client can access a user’s resources, typically by obtaining tokens. OAuth2 usually uses JWTs, but it doesn’t have to.
sequenceDiagram
participant RO as Resource Owner (User)
participant Client as Client App
participant AS as Authorization Server
participant RS as Resource Server
Client->>RO: Authorization Request (redirect to login)
RO->>AS: Authenticate + Grant Permission
AS-->>Client: Authorization Grant (code)
Client->>AS: Exchange Grant for Access Token
AS-->>Client: Access Token (JWT)
Client->>RS: API Request with Access Token
RS-->>Client: Protected Resource
Concept |
Purpose |
Example |
|---|---|---|
Authentication |
Verifying who the user is |
“Log in with email/password.” |
Authorization |
Determining what the user/app is allowed to do |
“Allow app to access your Google Drive files.” |
JWT |
A token format used to store claims |
|
OAuth2 |
A framework for authorization |
Google login, GitHub OAuth apps |
Main Roles#
Resource Owner: The user who owns the data.Client: The application requesting access.Authorization Server: Issues tokens after verifying the user.Resource Server: Hosts the protected resources (e.g., APIs).
Core Flow#
Authorization Request: The client asks the user for permission.Authorization Grant: The user approves and the client gets a grant (e.g., code).Access Token Exchange: The client exchanges the grant for an access token.API Access: The client uses the token to call the resource server.
import requests
from requests_oauthlib import OAuth2Session
# OAuth 2.0 client details
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
authorization_base_url = "https://example.com/oauth/authorize"
token_url = "https://example.com/oauth/token"
redirect_uri = "https://localhost/callback"
# Step 1: Redirect user to provider for authorization
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri)
authorization_url, state = oauth.authorization_url(authorization_base_url)
print("Please go to this URL and authorize:", authorization_url)
# Step 2: Get the authorization response URL from user
redirect_response = input("Paste the full redirect URL here: ")
# Step 3: Fetch the access token
token = oauth.fetch_token(token_url, authorization_response=redirect_response,
client_secret=client_secret)
print("Access Token:", token)
# Step 4: Access a protected resource
protected_url = "https://example.com/api/user"
response = oauth.get(protected_url)
print("Protected Resource:", response.json())
Please go to this URL and authorize: https://example.com/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Flocalhost%2Fcallback&state=nukc5oHUnqmZbbWoWtaLOO39noQTbA
---------------------------------------------------------------------------
MismatchingStateError Traceback (most recent call last)
Cell In[3], line 20
17 redirect_response = input("Paste the full redirect URL here: ")
19 # Step 3: Fetch the access token
---> 20 token = oauth.fetch_token(token_url, authorization_response=redirect_response,
21 client_secret=client_secret)
22 print("Access Token:", token)
24 # Step 4: Access a protected resource
File ~/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages/requests_oauthlib/oauth2_session.py:271, in OAuth2Session.fetch_token(self, token_url, code, authorization_response, body, auth, username, password, method, force_querystring, timeout, headers, verify, proxies, include_client_id, client_secret, cert, **kwargs)
268 raise InsecureTransportError()
270 if not code and authorization_response:
--> 271 self._client.parse_request_uri_response(
272 authorization_response, state=self._state
273 )
274 code = self._client.code
275 elif not code and isinstance(self._client, WebApplicationClient):
File ~/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py:220, in WebApplicationClient.parse_request_uri_response(self, uri, state)
176 def parse_request_uri_response(self, uri, state=None):
177 """Parse the URI query for code and state.
178
179 If the resource owner grants the access request, the authorization
(...) 218 oauthlib.oauth2.rfc6749.errors.MismatchingStateError
219 """
--> 220 response = parse_authorization_code_response(uri, state=state)
221 self.populate_code_attributes(response)
222 return response
File ~/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py:277, in parse_authorization_code_response(uri, state)
274 params = dict(urlparse.parse_qsl(query))
276 if state and params.get('state') != state:
--> 277 raise MismatchingStateError()
279 if 'error' in params:
280 raise_from_error(params.get('error'), params)
MismatchingStateError: (mismatching_state) CSRF Warning! State not equal in request and response.
Step-by-Step Minimal OAuth2 Server#
Collecting authlib
Downloading authlib-1.6.5-py2.py3-none-any.whl.metadata (9.8 kB)
Requirement already satisfied: starlette in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (0.48.0)
Requirement already satisfied: uvicorn in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (0.38.0)
Requirement already satisfied: cryptography in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from authlib) (46.0.3)
Requirement already satisfied: anyio<5,>=3.6.2 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from starlette) (4.11.0)
Requirement already satisfied: typing-extensions>=4.10.0 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from starlette) (4.15.0)
Requirement already satisfied: idna>=2.8 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from anyio<5,>=3.6.2->starlette) (3.11)
Requirement already satisfied: sniffio>=1.1 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from anyio<5,>=3.6.2->starlette) (1.3.1)
Requirement already satisfied: click>=7.0 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from uvicorn) (8.3.0)
Requirement already satisfied: h11>=0.8 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from uvicorn) (0.16.0)
Requirement already satisfied: cffi>=2.0.0 in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from cryptography->authlib) (2.0.0)
Requirement already satisfied: pycparser in /Users/hotronghai/Documents/Hai-Source/lectures-fs/.venv/lib/python3.12/site-packages (from cffi>=2.0.0->cryptography->authlib) (2.23)
Downloading authlib-1.6.5-py2.py3-none-any.whl (243 kB)
Installing collected packages: authlib
Successfully installed authlib-1.6.5
The Future of Authentication: Passkeys (2026 Update)#
While JWT and OAuth2 remain essential for API authentication, the industry is rapidly moving toward passkeys for user-facing login flows.
What Are Passkeys?#
Passkeys are a passwordless authentication method based on the FIDO2/WebAuthn standard. Instead of typing a password, users authenticate with their device’s biometric sensor (fingerprint, face) or screen lock (PIN).
sequenceDiagram
participant User as User (Browser)
participant Server as Your API
participant Auth as Device Authenticator
Note over User,Auth: Registration (one-time)
User->>Server: Request registration challenge
Server->>User: Send challenge + options
User->>Auth: Create credential (biometric)
Auth->>User: Public key + credential ID
User->>Server: Send public key (stored server-side)
Note over User,Auth: Login (every time)
User->>Server: Request login challenge
Server->>User: Send challenge
User->>Auth: Sign challenge (biometric)
Auth->>User: Signed assertion
User->>Server: Send signed assertion
Server->>Server: Verify with stored public key
Server->>User: JWT access token
Why Passkeys Matter#
NIST SP 800-63-4 (July 2025) recognizes passkeys as AAL2-compliant (same level as hardware tokens)
SMS OTP no longer satisfies AAL2 — phishing and SIM-swap attacks made it insufficient
Supported on iOS 16+, Android 9+, Windows 11, all modern browsers
Synced across devices via iCloud Keychain / Google Password Manager
How Passkeys Coexist with JWT/OAuth2#
Passkeys replace the login step (how users prove their identity), not the API authentication mechanism:
Layer |
Technology |
Role |
|---|---|---|
User login |
Passkeys (FIDO2/WebAuthn) |
Prove identity without passwords |
API authentication |
JWT tokens |
Stateless request authentication |
Delegated access |
OAuth2 + OIDC |
Third-party app authorization |
After a successful passkey login, your server still issues a JWT access token for API requests — the token flow remains the same.
Python Libraries#
py_webauthn— WebAuthn server-side library for PythonFastAPI integration: use
py_webauthnfor registration/login endpoints, then issue JWT tokens as before
You don’t need to implement passkeys from scratch for every project. Identity providers like Auth0, Okta, and Firebase Authentication already support passkeys. For most projects, delegate authentication to an identity provider and focus on API authorization with JWT.