OAuth 2.0: Open Authorization
In the past, sharing information between services was often done by sharing login credentials with the other service, trusting that service to protect the credentials and not exceed the intended scope of the shared information.
Now enter standards. Standards are documented, agreed-upon rules, guidelines, and best practices that dictate how software is written, tested, and maintained.
OAuth is a security standard that allows you to securely give one app permission to access your data in another application. It is similar to giving a person a keycard with specific permissions and bounds.
This is known as authorization, or delegated authorization, or simply delegation.
The keycard is not set in stone; you can revoke or take back the permission at any time you wish.
A real-life scenario: you have a matrix LED board that you want to show your GitHub activity graph on. The matrix board needs to access your GitHub data in order to display your activity graph. The connection between your matrix board and GitHub will be handled by OAuth 2.0.
Why OAuth 2.0? What Happened to 1.0?
OAuth 1.0, although really secure because it uses cryptographic signatures for every request, was not very developer-friendly. OAuth 2.0 simplified the experience significantly while keeping the security intact when implemented correctly.
The OAuth Flow
Terminologies
Before walking through the flow, here are the key players and concepts:
The Four Actors:
- Resource Owner (you) — the person who owns the data
- Client — the application that needs your data or wants to perform certain actions on your behalf
- Authorization Server — the application that knows you, or the application you can prove to that you really are you
- Resource Server — the application or service the client wants to use to perform actions on your behalf
Note: the Authorization Server and Resource Server could be part of the same thing, or could be separated entirely. In a case where they are separate, the Authorization Server has to trust the Resource Server, and vice versa.
Core Concepts:
- Redirect URI — the URL the Authorization Server will redirect the Resource Owner back to after permission is granted. It is set by the client and is also known as the callback URL
-
Response Type — the type of information the client wants to see. The most common type is
code, where the client expects to receive an authorization code. Other types includetoken,id_token, multiple responses, andnone - Scope — the granular permission the client wants, with respect to the data or the action they want to perform
- Consent — the Authorization Server builds out a consent form based on the scopes the client is requesting, and serves that form to the Resource Owner to decide whether to grant access or not
- Client ID — used to identify the client at the Authorization Server
- Client Secret — a secret password only the client and the Authorization Server know. It allows both to securely share information privately, behind the scenes. It must never be exposed on the frontend
- Authorization Code — a temporary, short-lived code the Authorization Server sends back to the client. The client then privately sends the authorization code back to the Authorization Server along with some other information, like the client secret, in exchange for an access token. This process is called the token exchange
- Access Token — the token the client will use to communicate with the Resource Server. It is short-lived by design — the less time it is valid, the less damage it can do if it gets stolen
-
Refresh Token — a long-lived token used to get a new access token when the current one expires, without making the user log in again. It never goes to the Resource Server — it only ever goes back to the Authorization Server. Because it travels less, it is much harder to steal. In most systems, the refresh token is stored in an
HttpOnlycookie on the backend, so JavaScript cannot touch it at all
The Auth Code Flow
Using the matrix board and GitHub as the example:
- You (Resource Owner) want your matrix LED board (Client) to access your GitHub data to display your GitHub graph
- Your matrix board sends a permission request to the GitHub Authorization Server — it adds the
client_id,redirect_uri,response_type, andscopeto the request - The Authorization Server prompts you with a consent form based on the scopes it got from the client
- You grant permission — the Authorization Server redirects back to the client using the
redirect_uri+ a temporary authorization code - The client securely sends its
client_id,client_secret, and the authorization code it just got, back to the Authorization Server - The Authorization Server verifies these and responds with an access token
- The access token is used to request resources from the GitHub REST API (Resource Server)
Prerequisite: Before any OAuth exchange, the client must have gotten a
client_idand maybe aclient_secretfrom the Authorization Server. This process is called app registration — that is the only way the Authorization Server can identify a client. Theclient_idandclient_secretare sometimes calledapp_idorapp_secret.
Step 2 — What the permission request actually looks like:
GET https://github.com/login/oauth/authorize
?client_id=abc123
&redirect_uri=http://localhost:5173/callback
&response_type=code
&scope=read:user%20user:email
&state=random_string_for_csrf_protection
Step 4 — What the redirect back to the client looks like:
GET http://localhost:5173/callback
?code=temporary_auth_code_here
&state=random_string_for_csrf_protection
Step 5 — What the token exchange request looks like (server to server):
POST https://github.com/login/oauth/access_token
{
"client_id": "abc123",
"client_secret": "supersecretvalue",
"code": "temporary_auth_code_here",
"redirect_uri": "http://localhost:5173/callback"
}
Step 6 — What the Authorization Server responds with:
{
"access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
"token_type": "bearer",
"scope": "read:user,user:email",
"refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169",
"expires_in": 28800
}
Step 7 — Using the access token to request data from the Resource Server:
GET https://api.github.com/user
Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a
PKCE — Proof Key for Code Exchange
The flow described above is usually for confidential clients — server or backend apps that can securely store the client secret.
PKCE is a security extension for the OAuth 2.0 protocol, built to ensure that the app asking for permission is indeed the one receiving the access token. It is used to keep public clients safe from code theft.
Public clients are applications that run in environments where credentials cannot be kept confidential, because the application code executes directly on the user's device or in the browser, and any embedded credentials can be decompiled, extracted, or inspected.
PKCE creates a dynamic, one-time-use secret for every single login flow. Since the secret changes every time, it is practically useless to a hacker even if intercepted.
The PKCE Flow
Very similar to the auth code flow, but with a little twist:
- The app creates two values — a
code_verifierand acode_challenge - The app sends the permission request + its
client_id,scope,redirect_uri,code_challenge, andcode_challenge_methodto the Authorization Server - The Resource Owner grants permission — the Authorization Server sends the authorization code to the client as usual
- The app sends its
client_id,code_verifier, and authorization code back to the Authorization Server in exchange for an access token - The Authorization Server hashes the
code_verifierand checks it matches thecode_challengeit received earlier — if it matches, it issues the access token
Step 2 — What the PKCE permission request looks like:
GET https://github.com/login/oauth/authorize
?client_id=abc123
&redirect_uri=http://localhost:5173/callback
&response_type=code
&scope=read:user%20user:email
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=random_string_for_csrf_protection
Step 4 — What the token exchange looks like with PKCE (no client secret needed):
POST https://github.com/login/oauth/access_token
{
"client_id": "abc123",
"code": "temporary_auth_code_here",
"redirect_uri": "http://localhost:5173/callback",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
In recent times, confidential apps are also now required to use PKCE as an additional layer of security.
OpenID Connect (OIDC)
OAuth 2.0 is designed only for authorization — for granting access to data and features from one app to another. OAuth is like giving the client a keycard, but it does not say anything about the keycard holder.
OIDC is a thin layer built on top of OAuth to add information about the logged-in user. It is like giving the client a badge that contains the permissions and some basic information about the Resource Owner.
OAuth is simply authorization (access) from one app to another, while OIDC is a client establishing a session (authentication).
When an Authorization Server supports OIDC, it is often called an Identity Provider (IdP).
OIDC also enables Single Sign-On (SSO) — where one login accesses multiple clients, all federated by one single identity.
OAuth is like an ATM + the internal banking infrastructure, while OIDC is like the ATM card — it carries basic information about the card owner.
The OIDC Flow
The flow is similar to the OAuth flow, but with a minimum requirement of adding openid to the scope in the permission request:
scope=openid%20profile%20email
In return, an id_token is returned together with the access token.
What the Authorization Server response looks like with OIDC:
{
"access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
"token_type": "bearer",
"expires_in": 28800,
"refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiTXVpeiBBZGV3YWxlIiwiZW1haWwiOiJtdWl6QGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8xMjM0NTYiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxNzE2MDAwMDAwLCJleHAiOjE3MTYwMDM2MDB9.signature"
}
ID Token — a JWT (JSON Web Token). The data inside it are called claims. When you decode that id_token above, here is what you actually get:
{
"sub": "123456",
"name": "Muiz Adewale",
"email": "muiz@example.com",
"picture": "https://avatars.githubusercontent.com/u/123456",
"email_verified": true,
"iat": 1716000000,
"exp": 1716003600
}
OIDC handles who you are. OAuth handles what you are allowed to do. Most Authorization Servers also handle authentication.
How the Frontend Actually Uses the ID Token
This is where it gets interesting for frontend developers, because there are two real-world patterns for reading user identity on the client side.
Pattern 1 — Decode the ID Token directly on the frontend
The ID Token is a JWT, which means it is just a Base64-encoded string. The frontend can decode it client-side and read the claims directly — the user's name, email, profile picture, and so on — without making an additional network request.
But here is an important distinction: decoding is not the same as verifying.
Anyone can decode a JWT. To actually trust its contents, you need to verify its signature — confirming it was genuinely issued by the Authorization Server and has not been tampered with.
This is where the OpenID Configuration endpoint comes in, commonly found at:
https://{the-auth-server}/.well-known/openid-configuration
This is a publicly available JSON document that describes the Authorization Server. Here is what it looks like:
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"scopes_supported": ["openid", "email", "profile"],
"response_types_supported": ["code", "token", "id_token"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}
The jwks_uri is where the Authorization Server's public keys live. Those public keys are what you use to verify the ID Token's signature before trusting any of the claims inside it.
In practice, most frontends do not do the verification themselves — that is the backend's job. The frontend decodes the token to read claims for display purposes only, trusting that the backend has already validated it.
Pattern 2 — Hit the /userinfo or /profile endpoint
Instead of decoding the ID Token on the frontend, the client can send the Access Token to the Authorization Server's /userinfo endpoint — or a custom /profile endpoint configured on the backend:
GET /userinfo
Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a
The server validates the token and returns the user's profile as a plain JSON object:
{
"sub": "123456",
"name": "Muiz Adewale",
"email": "muiz@example.com",
"picture": "https://avatars.githubusercontent.com/u/123456",
"email_verified": true
}
Both /userinfo and /profile are backend-configured. /userinfo is the OIDC spec standard — every OIDC-compliant Identity Provider is required to expose it. /profile is a custom backend endpoint that many systems expose to do the same job in their own way.
This pattern is simpler for the frontend — no JWT decoding needed, just a regular JSON response you can use directly.
Which pattern is more common?
Both are widely used. Pattern 1 is faster since there is no extra network call. Pattern 2 is simpler for the frontend and keeps JWT handling off the client entirely.
Putting It Together
| OAuth 2.0 | OIDC | |
|---|---|---|
| Purpose | Authorization (access) | Authentication (identity) |
| Answers | What can this app do? | Who is this user? |
| Token | Access Token | ID Token (JWT) |
| Use case | Delegated resource access | Login, SSO, user profile |
A Personal Note
Auth is something most frontend developers treat as a black box — something that happens on the backend, something you just hook into and move on. OAuth and OIDC feel like backend territory. I used to think the same.
But OAuth and OIDC are part of web security, and web security is not just a backend thing. It lives in every redirect, every token stored in memory, every scope decision your frontend participates in — whether you are aware of it or not.
Top comments (0)