OAuth is an open standard for authorization: it lets a user grant an application limited access to their data or actions in another service without sharing their password. When you let an app read your calendar or post on your behalf, that’s OAuth — the app gets a scoped, revocable access token instead of your credentials.
The key distinction: OAuth is authorization (what an app may do), not authentication (proving who you are). Authentication is added by OpenID Connect (OIDC), a layer on top of OAuth — so the familiar “Sign in with Google” button is really OIDC. Treating bare OAuth as a login system is a common mistake.
OAuth 2.0 defines four roles and several “flows.” This guide is the overview; the deep detail for each topic lives in the linked guides — part of our access management series.
In this article:
OAuth vs OIDC vs SAML vs API keys
| Standard | Purpose | Proves identity? | Typical use |
|---|---|---|---|
| OAuth 2.0 | Delegated authorization | No | App→API access, integrations |
| OIDC | Authentication on OAuth | Yes (ID token) | Modern login / SSO |
| SAML 2.0 | Authentication + SSO | Yes (assertion) | Enterprise SSO |
| API keys | App identification | No | Simple server-to-server |
User login → OIDC; delegated API access → OAuth; enterprise SSO → SAML or OIDC. For SSO specifically the real comparison is SAML vs OIDC (both authenticate users) — not OAuth, which only authorizes.
The four roles
- Resource owner — the user who owns the data and grants access.
- Client — the app requesting access. “Public” clients (browser/mobile) can’t keep a secret; “confidential” clients (backend) can — this drives the PKCE rules below.
- Authorization server — authenticates the user and issues tokens.
- Resource server — the API that accepts
Authorization: Bearer <token>and returns only what the token’s scopes allow.

How does OAuth work?
OAuth has the user approve access at an authorization server, which issues the client a short-lived access token instead of the password. The client sends that token with each request; the resource server validates it and returns only what the scopes permit.
The standard flow is Authorization Code with PKCE — the default for browser-capable interactive web, mobile and desktop applications:
- The client redirects the user to the authorization server’s
/authorizeendpoint with itsclient_id,scopes,redirect_uri, and a PKCEcode_challenge. - The user signs in and consents (the login step is where OIDC layers on).
- The server redirects back with a one-time authorization code.
- The client exchanges the code at the
/tokenendpoint over the back channel (confidential clients use their secret server-side; public clients use the PKCE verifier) for an access token, optionally a refresh token, and — with OIDC — an ID token. - The client calls the API with the bearer access token.
- The resource server validates it (by format — see tokens below) and responds.

The one-time code is exchanged on the back channel, so the token never rides a front-channel redirect — which is why this replaced the old Implicit flow. Other flows (client credentials, device code) suit other app types.
What is OAuth used for?
- Third-party / social login (“Sign in with Google”) — via OIDC on OAuth.
- API authorization — scoped access to your data in another service, no credentials shared.
- Machine-to-machine / microservices — services authenticate via Client Credentials.
- Mobile and desktop apps — remote user data via Authorization Code + PKCE.
- Delegated access for integrations — a tool acts for the user with scoped, revocable tokens.
Which grant should you use?
| Grant | Use when | Notes |
|---|---|---|
| Authorization Code + PKCE | Web, SPA, mobile and desktop apps using browser-based interaction | Default. Public clients MUST use PKCE; confidential clients SHOULD too. RFC 9700 allows a transaction-specific nonce as an alternative only for confidential OIDC clients, and only with the additional precautions it describes |
| Client Credentials | Machine-to-machine, no user | Scope to least privilege |
| Device Code | TVs, CLIs, IoT | User approves on another device |
| Refresh Token | Renew access without re-login | Optional — issued at the authorization server’s discretion (in OIDC, offline_access is one way to request them, not the only one); rotate + revoke |
| Implicit / Password (ROPC) | — | Don’t use for new work (Implicit: RFC 9700 SHOULD NOT; ROPC: MUST NOT) |
Shortcut: browser-capable user flow → Authorization Code + PKCE. Input-constrained device → Device Code. No user → Client Credentials.
Scopes, tokens, and refresh
- Scopes are a least-privilege control on what a token can do (not just a transparency label) — request the least you need.
- Access token may be opaque (validate by introspection at the authorization server) or a JWT (validate locally by signature). Don’t assume JWT.
- Refresh token obtains a new access token; issued only when the server grants one, and stored per client type (confidential: server-side; public: rotated or sender-constrained, per RFC 9700).
- ID token (OIDC) proves identity — never send it to an API as an access token.
How to use OAuth (example)
Register your app for a client_id, send the user to /authorize, get a code, exchange it at /token over the back channel, then call the API with the bearer token.
# 1. Authorize (user's browser)
GET https://auth.example.com/oauth/authorize?response_type=code
&client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback
&scope=openid profile read:data&code_challenge=S256_HASH&code_challenge_method=S256
# 2. Exchange code for tokens (back channel)
curl -X POST https://auth.example.com/oauth/token \
-d grant_type=authorization_code -d code=AUTH_CODE \
-d redirect_uri=https://yourapp.com/callback \
-d client_id=YOUR_CLIENT_ID -d code_verifier=ORIGINAL_VERIFIER
# → { "access_token":"...", "id_token":"...", "expires_in":3600 }
# The response may also include a refresh_token if the server issues one.
# 3. Call the API
curl https://api.example.com/data -H "Authorization: Bearer ACCESS_TOKEN"
Use PKCE: public clients MUST; confidential clients SHOULD. Use appropriate CSRF protection for the client and library — many implementations use PKCE together with a validated state value; OIDC flows may also use nonce. The openid scope makes this an OIDC request; if it succeeds, the response can include an ID token. Plain OAuth omits the openid scope. Machine-to-machine skips the redirect and uses grant_type=client_credentials.
OAuth 1.0 vs OAuth 2.0 vs OAuth 2.1
When people say OAuth today they mean OAuth 2.0. The three versions differ like this:
- OAuth 1.0 (RFC 5849) made the client sign every request with a cryptographic signature. That protected requests even without TLS, but it was hard to implement and debug. It is not compatible with OAuth 2.0 and is rarely used in new systems.
- OAuth 2.0 (RFC 6749) drops request signing in favor of bearer tokens sent over TLS, and adds grant types for different kinds of clients. It is simpler to build, but its security depends on getting the choices above right, which is why RFC 9700 exists.
- OAuth 2.1 consolidates OAuth 2.0 and its security best practices into one specification. It removes the implicit and resource owner password grants, requires PKCE for the authorization code flow, and requires exact redirect URI matching. See what’s new in OAuth 2.1.
Security (per RFC 9700)
- PKCE — public clients MUST use PKCE. Confidential clients SHOULD also use PKCE. RFC 9700 allows a transaction-specific
nonceas an alternative only for confidential OIDC clients, and only with additional precautions. - Redirect URIs — exact-match registered values; no wildcards/open redirects.
- Scopes — least privilege.
- Tokens — short access-token TTLs; rotate + revoke refresh tokens; never in URLs or logs; store per client type.
- Validation — every request: JWT → verify signature/
exp/iss/aud/scopes; opaque → introspect.
Full guidance: IETF RFC 9700 (OAuth 2.0 Security BCP).
OAuth for B2B SaaS (and where Frontegg fits)
B2B adds a tenant (account) dimension on top of OAuth: access and roles are scoped per organization. Tokens are tenant-scoped (the same user can have different access in different tenants); enterprise tenants federate their own IdP via SAML/OIDC; and per-tenant service accounts use Client Credentials. OAuth is the delegation layer — the CIAM platform provides the multi-tenant org model and IdP federation around it.
What OAuth provides — and what B2B SaaS must add
| Requirement | OAuth provides | B2B SaaS / CIAM must add |
|---|---|---|
| Delegated API access | Access tokens and scopes | — |
| User authentication | Through OIDC | Login policies and identity lifecycle |
| Tenant membership | Not defined by OAuth | Organization membership and tenant context |
| Roles and permissions | Not defined by OAuth | RBAC, permissions and entitlements |
| Enterprise SSO | Through OIDC/SAML federation | Per-tenant IdP configuration and administration |
| Machine access | Client Credentials | Tenant-specific service accounts and governance |
| Cross-tenant isolation | Not defined by OAuth | API enforcement of tenant boundaries |
Hosted vs embedded login is the main architecture choice. Hosted (the provider renders the login page) is the default — least code, centralized security, easiest to keep patched. Embedded (login UI inside your app) gives more brand/UX control but you own more of the surface. Running your own authorization server is real ongoing security work. Note that OAuth carries scopes, but what a role can do inside a tenant is an entitlements/RBAC concern layered on top — design them together.
Frontegg is an OAuth 2.0 / OIDC provider offering both hosted and embedded login (via its SDKs), tenant-scoped tokens, and per-tenant SSO and roles.
Tenant-aware authorization example. Maya belongs to both Acme and Beta. She is an administrator in Acme but only a viewer in Beta. After login, the API must validate the token, establish which tenant the request targets, verify that the token represents access to that tenant, and enforce Maya’s permissions within it. Checking only the user identifier would be insufficient — the same user has different access in each organization.
validate token
requestTenant = route.tenant
tokenTenant = token.tenant
if requestTenant != tokenTenant:
deny
if requiredPermission not granted:
deny
allow
Where organization context enters the OAuth flow:
User
→ Frontegg authorization server
→ authorization code
→ tenant-aware access token
→ application API
→ tenant + permission check
OAuth issues the token; the application still enforces tenant boundaries and permissions.
Next steps
Frontegg provides hosted OAuth/OIDC flows and SDK-managed authentication and session handling, with a multi-tenant model for B2B applications. To implement, start with Frontegg’s hosted vs embedded guide and the hosted OAuth/OIDC integration guide.
References
Protocol — OAuth 2.0 core RFC 6749; PKCE RFC 7636; Security BCP — PKCE public-MUST/confidential-SHOULD, Implicit SHOULD NOT, ROPC MUST NOT, refresh rotation, redirect exact-match RFC 9700; token introspection RFC 7662; OpenID Connect Core 1.0 (ID token, nonce, offline_access).
Product (Frontegg) — hosted vs embedded login, both via SDKs: hosted vs embedded; tenant-scoped tokens — tenantId is a required Frontegg claim, while roles and permissions are configurable via token templates (not unconditional defaults): JWT claims configuration; Frontegg Authentication product.
See additional guides on key access management topics
Attribute based access control (ABAC)
Authored by Frontegg
- What Is Attribute-Based Access Control (ABAC)?
- What is Role-Based Access Control: RBAC vs ABAC
- ABAC (Attribute-Based Access Control): Guide and Examples
Authentication
Authored by Frontegg
- Authentication: Methods, Protocols, and Strategies
- Authentication Apps: How They Work and 5 Apps to Know About
- All You Need to Know About JWT Authentication
Put this into practice with Frontegg
Frontegg is customer identity and access management (CIAM) for B2B SaaS. It's free up to 7,500 monthly active users, including 5 SSO/SCIM connections.
Start for freeThe Complete Guide to SaaS Multi-Tenant Architecture
Read the guide