Authentication

What Is OAuth and How Does It Work in Modern SaaS?

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.
The four OAuth roles: resource owner, client, authorization server, and resource server.
The four OAuth roles and the requests between them.

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:

  1. The client redirects the user to the authorization server’s /authorize endpoint with its client_id, scopes, redirect_uri, and a PKCE code_challenge.
  2. The user signs in and consents (the login step is where OIDC layers on).
  3. The server redirects back with a one-time authorization code.
  4. The client exchanges the code at the /token endpoint 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.
  5. The client calls the API with the bearer access token.
  6. The resource server validates it (by format — see tokens below) and responds.
OAuth Authorization Code flow with PKCE from authorization request through API access.
OAuth Authorization Code + PKCE flow.

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.

Security (per RFC 9700)

  • PKCE — public clients MUST use PKCE. Confidential clients SHOULD also use PKCE. RFC 9700 allows a transaction-specific nonce as 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

Together with our content partners, we have authored in-depth guides on several other topics that can also be useful as you explore the world of access management.

Attribute based access control (ABAC)

Authored by Frontegg

Authentication

Authored by Frontegg

Network Topology Mapping

Authored by Faddom

START FOR FREE

🤖 Explore this content with AI:

ChatGPT |
Perplexity |
Claude |
Google AI Mode |
Grok