Skip to content

How Authorization Works

Authentication verifies identity. But identity alone does not secure your system. Authorization is the other half that controls what each user can actually do once they're inside.

Table of Contents

In the previous post, we built a complete authentication system, one that verifies who a user is and proves it with a signed token. But knowing who someone is tells you nothing about what they're allowed to do.

A verified user and a free user are both authenticated. But both are not authorized to do the same things. That distinction is what this post is about.

Level 1 - Business Authorization

Suppose our platform has two types of users:

  • Free users
  • Verified paying users

We decide that only verified users can publish posts and free users can only view them. Now when a user sends: POST /create-post

Our backend must check whether this user is allowed to create a post. At this point, the backend already knows the user's identity from the validated ID token. It can extract a unique identifier, user_id = 42

Now the backend must determine the user's role. The simplest way is to store role information in our database:

user_id role
42 verified
50 free

When the request arrives, the backend performs a lookup: SELECT role FROM users WHERE user_id = 42

If the role is "verified," the request is allowed. If the role is "free," the request is denied.

This is business authorization. It is specific to our application. It represents product decisions. It defines what actions are allowed.

The Identity Provider does not decide this for us. The IdP only proves identity. Our backend enforces authorization rules.

Here is what this flow looks like:

This level introduces a very important separation. Authentication verifies identity. But identity alone does not secure your system. Authorization is the other half that controls what each user can actually do once they're inside.

Level 2 - Introducing an Internal Authorization Token

In Level 1, every time the user makes a request, the backend performs a database lookup to determine their role. For example: POST /create-post

Flow:

  1. Validate ID token.
  2. Extract user_id.
  3. Query database for role.
  4. Enforce rule.

This works, but it introduces an extra database call for every protected API request. As traffic grows, this can become inefficient. Each API call depends on a database lookup just to determine the user's role. Now we consider an optimization. Instead of looking up the role every time, we include the role directly inside a token as a claim.

But we cannot modify the ID token. It is signed by the Identity Provider. Any modification would invalidate the signature. So instead, we introduce a new step. After validating the ID token once, our backend creates its own internal token. This internal token contains: json { "user_id": 42, "role": "verified", "exp": 1700000000 }

This token is also signed by our backend. Token fields like role, user_id, and exp are called claims. These are structured pieces of information embedded inside a token. Claims appear in ID tokens, and as you'll see, in OAuth access tokens too.

Now the flow changes slightly.

  1. User authenticates at IdP.
  2. Backend validates the ID token.
  3. Backend fetches the user's role from the database.
  4. Backend generates its own internal authorization token.
  5. The browser stores this internal token.
  6. All subsequent API calls use this internal token.

No database lookup is required for authorization.

Here is the updated flow:

Now authorization becomes efficient.

  • The backend validates its own internal token.
  • It reads the role claim.
  • No database lookup is needed on every request.

This internal token bridges authentication (handled by the IdP) and business authorization (handled by our backend).

Even though the role is inside the token, our backend still enforces the rule: if role == "verified" → allow

The token carries information. The backend applies the decision logic. At this point:

  • Authentication is outsourced.
  • Business authorization remains ours.
  • Token issuance for authorization is still internal.

Level 3 - Introducing OAuth

At Level 2, we solved the performance problem. No more database lookups on every request. But we created a new responsibility: our backend is now a token issuer.

We maintain our own token generation logic. We sign tokens with our own keys. We decide when tokens expire. We handle token refresh flows.

Even though authentication was outsourced to the IdP, we are still running a mini authorization server inside our backend.

This works, but it introduces maintenance burden. Token management is subtle. Key rotation is a real risk. If your signing key is compromised, every token you've ever issued is at risk, and rotating keys requires coordinated updates across your entire infrastructure. Expiration policies and refresh logic add further complexity. These are not trivial security concerns to get right. If the Identity Provider already handles token issuance as a core feature, why duplicate that work?

This is where OAuth becomes useful.

Instead of our backend generating tokens, the Identity Provider issues standardized access tokens.

The flow changes:

  1. The user is redirected to the Identity Provider.
  2. The user authenticates.
  3. The IdP returns an authorization code.
  4. Our website exchanges the code for two tokens:
    • An ID token (proves identity)
    • An access token (grants API access)

Now all API calls use the access token: POST /create-post Authorization: Bearer <access_token>

The access token contains claims: json { "sub": "42", "role": "verified", "aud": "social-api", "exp": 1700000000 }

When the API receives a request, it:

  • Verifies the token signature using the IdP's public key
  • Checks the issuer and audience
  • Ensures the token hasn't expired
  • Reads the claims (like role)
  • Applies business rules

Here is the updated flow:

The critical shift: Our backend no longer generates tokens. It only validates tokens and enforces business rules.

Token issuance is now centralized at the Identity Provider, just like authentication.

OAuth is the standardized protocol that makes this possible. It defines:

  • How to request tokens
  • What tokens contain
  • How to verify tokens
  • How different applications trust the same issuer

At this level, the architecture becomes cleaner:

  • Authentication: centralized at IdP
  • Token issuance: centralized at IdP
  • Business authorization: enforced by our API

We are no longer maintaining our own token infrastructure. OAuth standardizes the entire flow.

Level 4 - Delegated Authorization (Third-Party Access)

So far, only our own applications are calling our APIs. As platforms grow, third-party integrations become inevitable. Developers want to build tools on top of your API, scheduling apps, analytics dashboards, cross-posting tools. Suppose an external developer builds an application that allows users to schedule posts on our platform.

This third-party app needs to: POST /create-post on behalf of the user.

Without OAuth, the only way to do this would be for the third-party app to ask the user for their username and password.

That is insecure for several reasons:

  • The user must trust the third-party app with credentials.
  • The third-party app can store or misuse the password.
  • There is no way to limit what the third-party app can do.
  • Revocation becomes difficult.

This is where delegated authorization becomes essential. Instead of sharing credentials, the user grants limited access.

The flow now looks like this:

  1. The third-party app redirects the user to the Identity Provider.
  2. The user authenticates.
  3. The user is shown a consent screen.
  4. The user grants permission (for example, "Allow this app to create posts").
  5. The IdP issues an authorization code to the third-party app.
  6. The app exchanges the code for an access token.
  7. The app calls our API using that access token.

Each permission the user grants is represented as a scope, a short string like posts:create or posts:delete. The access token will include only the scopes the user approved, giving the API a precise record of what the third-party app is allowed to do.

Here is the flow:

Notice what has changed. The access token now represents:

  • The user's identity
  • The granted permissions (scopes)
  • The specific client application

The third-party app never sees the user's password. The user can revoke access at any time. Access can be limited to specific actions (for example, only "create posts" but not "delete posts"). This is the core purpose of OAuth - standardized, secure delegation of API access.

Up to Level 3, OAuth simplified token issuance. At Level 4, OAuth enables controlled delegation between different applications. The architecture remains the same:

  • The IdP issues tokens.
  • The API validates tokens.
  • Business rules are enforced internally.

But now access is safely extendable beyond our own applications.

Comments

Latest

How Authentication Works

How Authentication Works

Exploring the evolution of authentication from basic login to sessions and tokens, federated identity, and single sign on, along with the architectural patterns that power modern identity systems.