ASK KNOX
beta
LESSON 250

OAuth 2.0 — The Authorization Dance

OAuth exists because sharing passwords is catastrophic. The authorization code flow, tokens, scopes, and redirect URIs — the full mental model behind every 'Login with Google' button you have ever clicked.

6 min read

Before OAuth, sharing access to your resources meant sharing your password.

You wanted an app to read your Google Calendar? You gave it your Google password. You wanted a third-party tool to post tweets? Your Twitter credentials lived in that tool's database. Every integration was a password handoff — and every password handoff was a catastrophic single point of compromise.

OAuth 2.0 solved this. The insight was simple: instead of sharing credentials, have the user prove to the identity provider that they authorize the app. The app never sees the password. It only gets a limited-scope token for the specific actions the user approved.

The Four Flows

OAuth 2.0 defines four grant types. Each is designed for a specific runtime environment.

FlowUse CaseHas Client SecretNotes
Authorization CodeWeb apps with a serverYesThe standard. Most secure.
Authorization Code + PKCESPAs, mobile, CLINoModern replacement for Implicit
Client CredentialsServer-to-serverYes (it IS the credential)No user involved
Implicit (legacy)SPAsNoDeprecated. Token in URL. Never use.

For any flow that involves a human user authenticating, you are choosing between Authorization Code (server-side apps) and Authorization Code + PKCE (client-side or native apps).

Client Credentials is for machines authenticating to APIs — your backend service authenticating to another backend service with no user in the loop.

The Authorization Code Flow

This is the flow behind every "Login with Google" button on a web app that has a server.

1. User clicks "Login with Discord"

2. App redirects user to Discord:
   GET https://discord.com/api/oauth2/authorize
     ?client_id=YOUR_APP_ID
     &redirect_uri=https://www.jeremyknox.ai/auth/callback
     &response_type=code
     &scope=identify email
     &state=RANDOM_CSRF_TOKEN

3. Discord shows the consent screen.
   User approves.

4. Discord redirects back to your app:
   GET https://www.jeremyknox.ai/auth/callback
     ?code=AUTHORIZATION_CODE
     &state=RANDOM_CSRF_TOKEN

5. Your server verifies state matches, then exchanges the code:
   POST https://discord.com/api/oauth2/token
     client_id=YOUR_APP_ID
     client_secret=YOUR_CLIENT_SECRET
     code=AUTHORIZATION_CODE
     grant_type=authorization_code
     redirect_uri=https://www.jeremyknox.ai/auth/callback

6. Discord responds with:
   {
     "access_token": "...",
     "refresh_token": "...",
     "token_type": "Bearer",
     "expires_in": 604800,
     "scope": "identify email"
   }

7. Your app stores the tokens, creates a session, user is logged in.

The authorization code is a one-time-use code that is only valid for a few minutes. It is not the actual access token — it is a receipt that your server exchanges for the real token. This exchange happens server-to-server, so the access token is never exposed in the browser's URL bar or history.

Key Concepts

Scopes define what the access token allows. identify email for Discord means the app can read the user's username and email. It cannot read their messages, join servers on their behalf, or anything else. Scopes are shown to the user in the consent screen.

The redirect URI is where the auth server sends the user after they approve (or deny) the request. It must be pre-registered with the provider. The server validates it exactly — a mismatch aborts the flow with an error.

The state parameter is your CSRF token. You generate a random value, store it in a session or cookie, include it in the authorization URL, and verify it matches when the callback arrives. Without it, an attacker can craft a malicious link that completes an OAuth flow for the attacker's account, authenticating them into your app as an unprivileged user (the login CSRF attack).

Authorization codes are single-use. If an attacker intercepts the callback URL (from browser history, referrer headers, or a compromised intermediary), the code is already spent. This is why the Implicit flow (which puts the access token directly in the URL fragment) is deprecated — tokens are long-lived and appear in browser history.

Refresh tokens allow the app to get a new access token when the current one expires, without asking the user to re-authenticate. They are long-lived and must be stored securely. Only the server should hold them.

Case Study: Discord OAuth Through Supabase GoTrue

In the jeremyknox.ai ecosystem, Discord OAuth goes through Supabase's GoTrue service — an open-source auth server that acts as an OAuth middleware layer.

The redirect chain has more steps than a direct OAuth flow:

User clicks "Login with Discord"
        ↓
app.jeremyknox.ai
        ↓  (redirect)
GoTrue (/authorize)
        ↓  (redirect to provider)
discord.com/api/oauth2/authorize
        ↓  (user approves, redirect back)
GoTrue (/callback?code=DISCORD_CODE)
        ↓  (GoTrue exchanges code with Discord server-to-server)
        ↓  (GoTrue creates its own session token)
        ↓  (GoTrue redirects to your app)
academy.jeremyknox.ai/auth/callback?code=GOTRUE_CODE
        ↓  (your app exchanges GoTrue code for session)
User is logged in

GoTrue intercepts the Discord callback before your app ever sees it. It exchanges the Discord authorization code for a Discord access token internally, then issues its own authorization code for your app to exchange. Your app never sees the raw Discord token — it only ever sees GoTrue tokens.

This indirection is powerful. It means you can add Google, GitHub, Apple, and email/password auth to the same app without changing your application logic. GoTrue normalizes them all into the same session format.

It also means your redirect URIs need to be configured in two places: in the Discord developer portal (pointing to GoTrue's callback endpoint) and in the Supabase dashboard (pointing to your app's callback endpoint).

Lesson 2 Drill

Pick any OAuth integration in your stack (or find one you use daily — "Login with GitHub" on any dev tool). Answer:

  1. Which grant type is being used?
  2. Where does the redirect URI point?
  3. What scopes are requested?
  4. Is there a state parameter in the authorization URL?

Open the browser's network tab, click the login button, and watch the redirect chain. You will see exactly where the authorization code appears and disappears.