<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Delegated Access

Delegated Access

So far the app authenticates users itself. Delegated access is the opposite problem: letting one service act on a user's behalf at another service ("Sign in with Google," or "let this app read your GitHub repos") without ever handing over the user's password to that other service.

Before OAuth, apps literally asked for your Gmail password so they could log in as you and scrape what they needed: total access, no revocation short of changing your password, and a third party storing your credentials. OAuth's design is a reaction to that.

OAuth 2.0

OAuth 2.0 is the framework for this. Its whole point is delegated authorization: granting an app limited access to your data somewhere else. Four roles:

  • Resource owner: you, the user. The data is yours; only you can grant access to it.
  • Client: the app that wants access. It never sees your password.
  • Authorization server: where you log in and consent (Google's, GitHub's). The only party that ever handles your credentials.
  • Resource server: the API holding your data. It accepts access tokens, not logins.

Concretely: a scheduling app (client) wants to read your (resource owner) calendar from the Google Calendar API (resource server), so it sends you to accounts.google.com (authorization server) to approve it.

What you approve there are scopes : named slices of access like calendar.readonly or repo. Scopes are least privilege made visible: the consent screen lists them, the access token is limited to them, and the resource server rejects anything outside them.

The main flow is the Authorization Code flow:

  1. Client redirects you to the authorization server (login + consent screen)
  2. You approve the requested scopes (e.g. "read your repos")
  3. Authorization server redirects back with a short-lived authorization CODE
  4. Client exchanges the code (+ its secret, or a PKCE verifier for public clients) for an ACCESS TOKEN , server-to-server
  5. Client calls the resource server with the access token
Browser App Auth server API log in + approve scopes 302 redirect: ?code=abc123 front channel: visible in the URL ?code=abc123 hits the redirect URI POST /token (code + secret or PKCE verifier) access token back channel: server-to-server, never in a URL GET /user/repos + access token 200: the repos you consented to the code travels through the browser; the access token never does
The authorization code rides through the browser where it can be seen; the access token is exchanged server-to-server and never touches a URL.

The two-step dance (code first, then exchange it for a token on the back channel) exists so the access token never travels through the browser/URL, where it could be logged or leaked. The code is useless on its own; redeeming it requires the client's secret.

The access token is usually short-lived and paired with a refresh token the client redeems on the back channel when it expires. Revocation works because the authorization server keeps state: when you remove an app from Google's "third-party access" page, its refresh token dies there.

OIDC

Here's the catch people trip on: OAuth is authorization, not authentication. An access token proves an app was granted access; it doesn't reliably tell you who the user is. Apps that used raw OAuth as a login mechanism opened subtle security holes by assuming "has a token" meant "is this user."

OpenID Connect (OIDC) is a thin identity layer on top of OAuth 2.0 that adds real authentication. It introduces:

  • An ID token : a JWT containing identity claims (sub, email, name), signed by the provider, meant specifically to tell the client who logged in.
  • A standard /userinfo endpoint and standard scopes (openid, profile, email).

"Sign in with Google" is OIDC, not bare OAuth, and it's your first taste of single sign-on (SSO) : authenticate once at a provider you already trust, and carry that proven identity into any app that trusts the provider. One critical point: the ID token must be verified, not just decoded: check the signature against the provider's public keys, and validate the iss, aud, and exp claims. Accepting an unverified or mis-targeted ID token re-opens the same identity confusion vulnerabilities that raw OAuth had. The mental split worth keeping:

OAuth 2.0 OIDC
Answers What may this app access? Who is this user?
Concern Authorization (delegated access) Authentication (identity)
Gives you Access token (for calling APIs) ID token (a JWT identifying the user)
Relationship The framework A layer built on top of OAuth

The tokens split the same way: the access token goes to APIs, the ID token stays with the client as proof of who logged in. Never accept an ID token as an API credential, and never treat possession of an access token as a login.

OIDC covers the consumer and modern-app use case. In enterprise environments, two older standards play the same SSO role. SAML (Security Assertion Markup Language) is the XML-based protocol used by corporate Identity Providers (Okta, AD FS, Azure AD): the user authenticates once at the IdP, which issues a signed XML assertion to the app — no password ever reaches the app. LDAP (Lightweight Directory Access Protocol) is the underlying directory protocol — most commonly Microsoft Active Directory — where user accounts, groups, and attributes live; the IdP typically reads from it to power SSO. The two complement each other: LDAP stores the identities, SAML carries them to web applications.

PKCE

The Authorization Code flow assumes the client can keep a secret. OAuth calls these confidential clients: server-side apps whose code and config live where users can't see them. A public client (a single-page app or a mobile app) can't keep anything: its code ships to the user's device, so any secret baked into it is visible. That breaks step 4's security, because an attacker who intercepts the authorization code could redeem it themselves.

PKCE (Proof Key for Code Exchange, "pixy") closes that gap without a stored secret:

import crypto from "node:crypto";

// Before redirecting, the client makes a one-time secret and sends only its hash
const verifier  = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");

// 1. Authorize request includes:  code_challenge=<challenge>&code_challenge_method=S256
// 2. Token exchange includes:     code_verifier=<verifier>
//    The server hashes the verifier and checks it matches the challenge it saw earlier.
App Auth server Attacker GET /authorize + code_challenge=SHA256(v) code remembers the challenge intercepts the code POST /token (code, no verifier) 400: hash mismatch POST /token (code + verifier v) SHA256(v) == challenge ✓ access token the verifier never leaves the app; a stolen code redeems nothing
The verifier never leaves the legitimate app. An attacker who intercepts the code has nothing to redeem it with: the auth server hashes the presented verifier and compares it to the challenge from step one.

An intercepted code is now worthless: redeeming it requires the original verifier, which never left the legitimate client. Generated fresh for each flow, held in memory, gone when the exchange completes. PKCE started as a mobile/SPA fix and is now recommended for all OAuth clients.

Check your understanding

Question 1 of 3

Why use OIDC instead of plain OAuth for login?

A team builds "Log in with Acme" using plain OAuth: if the app receives a valid access token, it treats the session as that user. Why is OIDC the right tool instead?