# JWT, IAM, and Auth0: Authentication and Authorization Explained (Without the Confusion)

> Demystify JWT, IAM, and Auth0 — learn authentication vs. authorization, roles, scopes, and secure access for apps, APIs, and microservices.

- Source: https://parserai.co/blog/jwt-iam-and-auth0-authentication-and-authorization-explained-without-the-confusion
- Published: 2026-02-24 (updated 2026-10-31)

---
Modern software rarely lives in one place — a single product spans web and mobile clients, APIs, microservices, third-party integrations, and several cloud environments. In that world the question is never just *“Who are you?”* It is also *“What are you allowed to do?”* and *“How do we prove it securely across systems?”*

Authentication, authorization, JWT, IAM, and platforms like Auth0 all answer pieces of those questions — and they are constantly used interchangeably, which is exactly where brittle security designs come from. Confusing the terms leads to over-permissive access, duplicated logic, and painful rework. This guide separates the concepts cleanly, shows how they connect, and gives you patterns you can drop into a real architecture.

## Authentication vs. authorization: the distinction that fixes everything

**Authentication** verifies identity — it answers *“Are you really Alice?”*through passwords, MFA, passkeys, biometrics, social login, or SSO. Its output is a verified principal: a user or service the system now trusts.

**Authorization** decides permissions — *“Can Alice view invoices? Can she delete users? Can this service call that endpoint?”* Its output is a yes/no decision, usually qualified by scopes, roles, or policies. The one-line version worth memorizing:

Authentication is who you are. Authorization is what you are allowed to do.

— The rule of thumb for every access decision

## What IAM actually covers

**IAM stands for Identity and Access Management** — occasionally written IdAM, and said as the three letters rather than as a word. The abbreviation names a discipline, not a product. When a vendor says “our IAM,” they mean their slice of it. When a security team says IAM, they mean all of it: every mechanism that establishes who or what an identity is, and every mechanism that decides what that identity may reach.

So it is broader than any single login screen — the systems and processes for managing identities and controlling access across users, devices, services, and workloads. A mature IAM practice typically includes:

- Identity lifecycle — provisioning and deprovisioning users automatically
- Authentication mechanisms — SSO, MFA, passwordless
- Authorization models — RBAC, ABAC, and policy-based access
- Directory services and user stores
- Audit logs and compliance reporting
- Secrets, keys, and credential policies

In an enterprise, IAM is what lets employees sign in to every internal tool through one SSO, revokes a contractor's access the moment their contract ends, lets APIs authenticate one another, and enforces least privilege across thousands of individual permissions.

## How IAM works, step by step

Described as a list of capabilities, IAM sounds abstract. Followed through one request, it is concrete and short. Six stages run every time someone or something touches a protected resource, and each one belongs to a different part of your architecture:

| Stage | What happens | Who owns it |
| --- | --- | --- |
| 1. Provision | An identity is created and attached to attributes: department, tenant, employment status, or for a machine identity, the service it represents | HR system or directory, synced to the IdP |
| 2. Authenticate | The identity proves itself — password plus MFA, passkey, SSO assertion, or a client credentials exchange for a service | The identity provider |
| 3. Issue | A session or token is minted carrying the claims the rest of the chain will read | The identity provider |
| 4. Decide | A policy is evaluated: these claims, this action, this specific resource — allow or deny | Your API, or a policy engine beside it |
| 5. Enforce | The decision is applied at the point the data is actually served, not at the point the button is drawn | Your API |
| 6. Audit and deprovision | The decision is logged with enough context to reconstruct it later, and access ends when the identity does | Logging pipeline plus the directory |

Two of those stages are where designs usually go wrong. Stage 4 gets collapsed into stage 3 — the team treats the presence of a claim as the decision, so whoever holds a token holds the permission. And stage 6 gets skipped, which is invisible until an auditor asks who could read a given record last March and nobody can answer.

Notice also that the boundary between stages 3 and 4 is the boundary between two vendors-worth of responsibility. Your identity platform owns everything up to issuing the token. Everything after it is code you write. That split is the single most useful thing to hold in your head when reading any IAM documentation, including Auth0's.

## What a JWT is (and what it is not)

A **JWT (JSON Web Token)** is a compact, URL-safe token format used to carry claims about a user or client — most often for API access. Standardized as RFC 7519, it has three base64url-encoded parts: a **header** (token type and signing algorithm), a **payload** (the claims), and a **signature** that proves integrity and authenticity. Decode a real access token's payload and it looks like this:

jwt-payload.json — the decoded middle segment

```
{
  "sub": "auth0|a1b2c3d4e5",
  "iss": "https://login.example.com/",
  "aud": "https://api.example.com",
  "iat": 1740411449,
  "exp": 1740415049,
  "scope": "read:invoices write:invoices",
  "roles": ["finance_admin"],
  "tenant": "tenant_042"
}
```

JWTs are popular because they are **stateless** (an API can validate one without a database lookup), **portable** (they travel in an HTTP header), and **verifiable** (signed, and encrypted with JWE when needed). But a JWT is not “authentication” by itself — it is *evidence* that authentication already happened, issued by an identity provider after a successful login. Authentication happens at the IdP; authorization decisions happen at your API, using the token's claims plus your own policy.

Encoded is not encrypted

A standard JWT payload is base64url-encoded, not encrypted. Anyone holding the token can read every claim. Never put secrets, passwords, or sensitive PII in a JWT unless you are explicitly using JWE and the design demands it.

## Where Auth0 fits

**Auth0** is an identity platform — an IdP and CIAM solution — that helps teams implement authentication flows and issue tokens (usually JWTs) for apps and APIs. It handles user authentication (database users, social login, enterprise SSO), MFA, ID- and access-token issuance, sessions and refresh-token rotation, and customizable identity workflows through rules and actions.

What it does *not* do is own all of your authorization. Auth0 can store roles and permissions and stamp them into token claims, but fine-grained, contextual, resource-level authorization is still enforced at your API layer or in a dedicated policy engine. Treat the claims Auth0 issues as trusted *inputs* to your decision — not the decision itself.

### IAM basics in Auth0's vocabulary

Much of the confusion around “IAM in Auth0” is vocabulary. The general IAM concepts above all exist in Auth0, but under product names that do not always match. Mapping one onto the other makes the dashboard, the docs and the token claims read as the same system:

| IAM concept | Auth0 term | Where it shows up in a token |
| --- | --- | --- |
| Identity provider / authorization server | Tenant (your Auth0 domain) | **iss** — the tenant's URL |
| User store or upstream identity source | Connection (database, social, enterprise) | Prefix of **sub**, such as `auth0\|` or a social provider name |
| Client — the app asking for access | Application | **azp** — the application's client ID |
| Protected resource | API, identified by its Identifier | **aud** |
| Permission | Permission defined on an API | **scope** and, with RBAC on, **permissions** |
| Role | Role (a bundle of API permissions) | Not by default — added through an Action as a namespaced claim |
| Business customer / B2B tenant | Organization | **org_id** |
| Policy hook at sign-in | Action on the post-login trigger | Any custom claim it sets, or a denied login |

Two consequences follow. First, “IAM” in Auth0 is not one menu: it is the combination of connections, applications, APIs, roles and Actions. Second, roles are a dashboard concept until you choose to put them in the token — your API sees permissions and claims, never the role object itself, which is one more reason to authorize on permissions rather than role names.

### Auth0 authentication vs. authorization, concretely

The comparison people search for is really a question about where a boundary sits. Auth0 has features on both sides of it, which is exactly why the two get conflated. Splitting them by what the platform can *know* makes the line obvious:

| Auth0 handles (authentication side) | Your API handles (authorization side) |
| --- | --- |
| Verifying credentials, MFA, passkeys, social and enterprise SSO | Whether this request may touch this record |
| Sessions, refresh-token rotation, logout across apps | Whether the record belongs to the caller's tenant |
| Issuing ID tokens and access tokens, signing them, publishing the keys | Validating that signature and every registered claim |
| Storing roles and permissions, stamping them into claims | Deciding what those roles mean for a given operation |
| Coarse gating: which applications may request which API's scopes | Contextual gating: amount thresholds, time windows, data class, device trust |

Mechanically, the Auth0 side works by registering your API with an identifier that becomes the token's **aud** claim, defining permissions on it, assigning those permissions to roles, and enabling RBAC so the granted permissions ride along in the access token. Anything you add beyond the registered claims has to be namespaced with a URI-shaped key, because unnamespaced custom claims are dropped:

access-token-claims.json — registered claims plus namespaced custom claims

```
{
  "iss": "https://login.example.com/",
  "sub": "auth0|a1b2c3d4e5",
  "aud": ["https://api.example.com", "https://login.example.com/userinfo"],
  "exp": 1740415049,
  "azp": "3kL9xQ2mNpR7vT",
  "scope": "read:invoices write:invoices",
  "permissions": ["read:invoices", "write:invoices"],
  "https://api.example.com/tenant": "tenant_042",
  "https://api.example.com/cost_centers": ["CC-110", "CC-240"]
}
```

Your API fetches the signing keys from the tenant's JWKS endpoint, caches them by key ID, and pins the expected algorithm rather than reading it out of the token header. Then it treats **permissions** and the namespaced tenant claim as two of several inputs — never as the answer. The token says this caller may write invoices somewhere. Only your database knows whether invoice 4471 is one of them.

## Validating an Auth0 JWT in your API, step by step

An Auth0 access token for a registered API is a JWT signed with **RS256** by default: the tenant signs with a private key, and anyone can verify with the public key it publishes. That asymmetry is the point — your API never holds a secret that could mint tokens. Decode the first segment of a token and you see which key to use:

jwt-header.json — the decoded first segment

```
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "nYx2b8qL0f3Kp"
}
```

The **kid** (key ID) names one key in the tenant's JSON Web Key Set, published at `https://YOUR_DOMAIN/.well-known/jwks.json` and also discoverable from the tenant's `/.well-known/openid-configuration` document. A correct validator does the same eight things on every request, in this order, and rejects at the first failure:

- **Read the bearer token** from the `Authorization` header; reject anything missing or malformed with 401.
- **Pin the algorithm.** Accept RS256 only. Never let the header's **alg** choose — that is how “alg: none” and key-confusion attacks work.
- **Find the key by kid** in a cached copy of the JWKS. On an unknown kid, refetch once (keys rotate), with a rate limit so a flood of forged kids cannot hammer the endpoint.
- **Verify the signature** with that public key.
- **Check iss** equals your tenant URL exactly, including the trailing slash.
- **Check aud** contains your API's Identifier. A token minted for another API on the same tenant is validly signed and still not yours.
- **Check exp and nbf** with a small clock-skew allowance, typically under a minute.
- **Check the permission** the endpoint needs is in **scope** or **permissions** — then hand over to the resource-level check against your own data.

Steps 1–7 are authentication of the *token*; only step 8 begins authorization, and it is the coarse half. Use a maintained JOSE/JWT library for steps 2–7 rather than hand-rolled parsing, and configure it with the issuer, audience and algorithm explicitly instead of relying on defaults. Access tokens for a new Auth0 API default to a 24-hour lifetime; for anything that touches money or personal data, shorten it and lean on refresh-token rotation instead.

The checks people skip

Skipping **aud** is the most common gap: every API on the tenant then accepts every other API's tokens. The second is caching the JWKS forever, which turns a routine key rotation into an outage.

## Auth0 authorization: the four levers

When people search for “Auth0 authorization,” they usually mean one of four distinct features. Each moves a different amount of the decision into the identity platform:

| Lever | What it does | Use it for |
| --- | --- | --- |
| RBAC on an API | With “Enable RBAC” and “Add Permissions in the Access Token” switched on, the permissions granted through a user's roles ride in the **permissions** claim | Coarse, operation-level gates: may this caller write invoices at all |
| Post-login Actions | Code that runs at sign-in, can deny the login or add namespaced claims such as tenant or role names | Context the API needs on every call; blocking sign-ins that break policy |
| Organizations | Models B2B customers as organizations with their own members and roles, stamped as **org_id** | Multi-tenant SaaS where each customer is a tenant of yours |
| Fine-grained authorization (FGA) | A separate relationship-based service, built on the open-source OpenFGA, that answers “can user X do Y on object Z” at request time | Per-record sharing and hierarchies that would bloat a token |

The first three all put data *into the token* at sign-in, so they are fixed for the token's lifetime and blind to which record is requested. The fourth is a runtime check your API calls. That is the line to design around: anything that can change during a session, or depends on the specific object, belongs in a runtime check — whether that is an FGA call or a tenant-scoped query in your own database.

ID token vs. access token

The ID token describes the user and is for your application to consume. The access token is for your API and is the only one it should accept. Sending an ID token to an API — or accepting one there — is a common and avoidable mistake.

## How they work together: the typical flow

For a web or mobile app calling an API, the pieces line up in a predictable sequence:

- The user signs in via Auth0 — that is authentication.
- Auth0 issues an access token, usually a JWT.
- The app calls your API with an **Authorization: Bearer <token>** header.
- Your API validates the signature (trust), verifies **iss**, **aud**, and **exp** (correctness), then checks scopes, roles, and claims (authorization).
- The API returns data only if the request is permitted.

Drawn as a diagram, the identity and access management flow with Auth0 has one boundary that matters — the line between what the identity platform proves and what your API decides:

iam-flow.txt — identity and access management with Auth0, as a diagram

```
User / service
       │  1. sign in (password + MFA, SSO, passkey,
       │     or client credentials for a machine)
       ▼
  ┌─────────────────────────────┐
  │  Auth0 tenant  (the IdP)    │   AUTHENTICATION
  │  connections · Actions      │   who is this?
  │  roles → permissions        │
  └─────────────────────────────┘
       │  2. access token (JWT): iss, aud, exp,
       │     scope, permissions, namespaced claims
       ▼
  Client app ── 3. Authorization: Bearer  ──┐
                                                   ▼
  ┌──────────────────────────────────────────────────┐
  │  Your API                                        │  AUTHORIZATION
  │  4. verify signature against JWKS (kid)          │  may this caller
  │  5. check iss · aud · exp                        │  do this, to this
  │  6. check permission for the endpoint            │  record, now?
  │  7. resource policy: tenant, owner, amount       │
  │  8. log the decision                             │
  └──────────────────────────────────────────────────┘
       │  allowed → data      denied → 403
```

_Illustrative validation checklist an API runs on every request before returning data._

Never trust the frontend to hide power

Even with a valid JWT, the API must enforce authorization at the endpoint and resource level. Hiding an admin button in the UI is not access control — a determined caller sends the request anyway.

## Authorization models you will meet

JWT-based systems typically lean on three models, often combined:

| Model | How it decides | Trade-off |
| --- | --- | --- |
| RBAC (role-based) | Users get roles — admin, manager, viewer — that map to permissions | Simple and common, but rigid when access depends on context |
| ABAC (attribute-based) | Decisions weigh attributes: department, location, risk, device trust, data class | Flexible and powerful, but harder to implement and audit |
| Scopes (OAuth 2.0) | Tokens carry scopes like read:orders or write:orders | Ideal for API operations; needs consistent scope design across services |

Mature architectures blend them: roles for coarse access, scopes for API operations, and ABAC policies for the sensitive decisions that depend on *which* record is being touched.

## What an IAM role actually is

A **role** is a named bundle of permissions you attach to an identity so you can reason about people instead of about individual grants. “Finance admin” is a role; `write:invoices` is a permission; the role exists so that onboarding an analyst is one assignment rather than forty. Three distinctions are worth keeping straight, because the words get used loosely:

- **Role vs. group.** A group is a collection of identities; a role is a collection of permissions. Groups often map onto roles, but the mapping is a choice, not a given.
- **Role vs. scope.** A role describes what the person may do. An OAuth scope describes what the person has authorized *this application* to do on their behalf. A user with a finance role using a read-only integration should still be limited by the scope.
- **Role vs. policy.** A role is static — it does not know which record you asked for. A policy can weigh the request itself. Roles answer “what kind of user is this”; policies answer “is this particular action allowed right now.”

Roles fail in two predictable directions. They multiply — one role per client, per region, per exception, until nobody can say what a given role grants and reviews become theatre. Or they stay small and get stretched, so a single broad role covers a job it was never scoped for. Both failures produce the same symptom: a valid token that reaches far more than its holder needs.

_Illustrative: reach of one over-broad role claim, before and after resource-level policy checks are added at the API._

The fix is not to abandon roles. It is to keep them coarse and few, put operation-level detail in scopes, and leave every question that depends on *which* record to a policy check that runs against your own data. Roles get a caller into the right building. They are not supposed to open every door inside it.

## Where auth actually goes wrong

The failures rarely come from a broken token format. They come from the checks teams skip around it — accepting a token without fully validating it, handing out scopes far broader than a client needs, or checking that a user is a “finance_admin” but never checking whether *this* invoice belongs to *their* tenant.

_Illustrative breakdown of where token-based access failures tend to originate._

## Security essentials for JWT and IAM

- **Validate tokens fully.** Check the signature with the correct algorithm and key, then iss, aud, exp (with clock skew), token type, and the required scopes or roles.
- **Keep tokens short-lived.** A short expiration shrinks the blast radius of a stolen token. Use refresh tokens carefully, especially on SPAs and mobile.
- **Never authorize on a claim alone.** Claims are inputs. Still ask: can this user access this specific invoice ID, in this tenant?
- **Design for revocation.** Stateless tokens are efficient but hard to revoke — pair short lifetimes, rotating refresh tokens, and introspection or deny-lists for high-risk flows.
- **Keep secrets out of the payload.** Assume every claim is readable by whoever holds the token.

## IAM security beyond the request

That checklist secures a single request. IAM security is also about the state of the system between requests — the accounts nobody uses, the keys nobody rotates, the permissions that accumulate as people change teams. These are the controls that tend to matter in a real review:

- **Least privilege as the default, not the cleanup.** New roles start with nothing and earn permissions. The opposite order never converges, because nobody volunteers to have access removed.
- **Privilege drift.** Someone who moves from AP to controllership keeps both sets of permissions unless something takes the first away. Re-certify access on a schedule and on role change, and make the reviewer someone who would notice.
- **Machine identities outnumber people.** Service accounts, CI runners and integrations are identities too, usually with broader scopes and longer-lived credentials. They deserve the same expiry, rotation and inventory as staff.
- **Key rotation and algorithm pinning.** Fetch signing keys from JWKS and cache by key ID so rotation is a non-event. Pin the expected algorithm server-side; never let a token's own header choose how it is verified.
- **Tenant isolation as a hard boundary.** In a multi-tenant system, the tenant check belongs in the query, not in a conditional that a future refactor can drop.
- **Audit trails you can actually query.** Log the decision, the claims it rested on and the resource, not just the fact that a request arrived. The test is whether you can answer “who could have read this record, and did they” without a migration.
- **A rehearsed revocation path.** Know in advance how you kill a session, a refresh token and an API key, and how long each takes to propagate. Finding out during an incident is expensive.

Not “who has access?” but “who would still have access if we were wrong about someone?”

— The question behind every access review

## Why this matters for document-heavy workflows

Many teams first hit serious IAM and authorization complexity when they digitize high-volume, sensitive workflows — invoices, receipts, contracts, and identity documents. These processes move PII, financial data, and contractual terms, so access has to guarantee that only authorized roles can upload or view certain document types, that extracted fields land only in permitted systems, and that a full audit trail exists for compliance.

That is the setting for a document extraction service such as Parser, which returns the fields you define from a document through a REST API. The service reads the document; who may send it, which roles may see the result, where the fields are allowed to land and what is logged are decisions of the system around it. Design that part with the same care as the extraction itself.

The practical shape of that is covered elsewhere on this blog. Our [guide to integrating document extraction via API](https://parserai.co/blog/how-to-integrate-automatic-document-extraction-into-your-system-via-api-a-practical-guide-for-technical-teams) walks through bearer tokens, key rotation and least-privilege credentials in the context of an extraction pipeline. And because extracted fields flow onward into payment runs and ERP writes, it is worth reading how [a malicious document can try to steer that pipeline](https://parserai.co/blog/prompt-injection-document-extraction-pipeline-defense) — an authorization boundary is what stops an attacker-chosen field from becoming an attacker-chosen action.

3

JWT parts: header, payload, signature

RFC 7519

the standard behind JWT

2

layers: authn at the IdP, authz at the API

## Common questions

### What does IAM stand for?

Identity and Access Management, sometimes written IdAM. It refers to the whole discipline of managing identities and controlling what they can reach — not to one product or one login box.

### How does IAM work?

In six stages: an identity is provisioned, authenticated, and issued a token or session; then a policy decides whether a specific action on a specific resource is allowed, that decision is enforced where the data is served, and it is logged. The identity provider owns the first three; your own code owns the rest.

### What is an IAM role?

A named bundle of permissions assigned to an identity, so access is granted by job rather than one permission at a time. A role is static and does not know which record was requested — keep roles coarse and let a policy check handle resource-level decisions.

### Is Auth0 authentication or authorization?

Primarily authentication, plus the storage and issuance of authorization data. It verifies identity and stamps roles, permissions and scopes into tokens; deciding what those mean for a given record stays with your API.

### What is the difference between an ID token and an access token?

The ID token tells your application who signed in. The access token authorizes calls to an API and is the only one an API should accept. They are both JWTs, which is why they get mixed up.

### How do I validate an Auth0 JWT?

Fetch the public keys from your tenant's `/.well-known/jwks.json`, pick the one matching the token's **kid**, verify the RS256 signature with the algorithm pinned server-side, then check **iss**, **aud** and **exp**, and finally the permission the endpoint requires.

### What is the difference between JWT and OAuth?

JWT is a token *format*. OAuth 2.0 is an authorization *framework* that defines how clients obtain access to APIs. OAuth access tokens can be JWTs, but they do not have to be.

### Is Auth0 an IAM solution?

Auth0 is an identity platform widely used for authentication, SSO, and token issuance. In many organizations it functions as a key part of an IAM strategy — especially for customer identity and application authentication — rather than the entire IAM stack.

### Does a JWT prove a user is authenticated?

It can indicate authentication occurred, if it was issued by a trusted identity provider. Your API must still validate the token and enforce authorization rules before granting access.

### What should be stored in a JWT?

Only what the API needs to make decisions — user ID, issuer, audience, expiration, and the scopes or roles that matter. Avoid sensitive data unless the token is encrypted and the design explicitly requires it.

## Bringing it together

IAM is the overarching approach to identity and access. Authentication confirms who someone is; authorization controls what they can do. A JWT is a verifiable, portable way to carry the claims that inform those decisions — often as an OAuth access token — while Auth0 helps you implement secure authentication and token issuance at scale. Do all of that well, add resource-level checks the token can't make on its own, and the buzzwords become what they were meant to be: the foundation for scalable, secure digital operations.

Parser

## See it on your own documents.

In the demo we process your documents live — no slides, no fluff.

[Book a demo](https://parserai.co/demo)
