The authorization framework behind “Sign in with Google,” Stripe Connect, and almost every third-party integration on the modern web — with the OIDC distinction, grant types, and security mistakes that bite SaaS founders.
Research-based overview. This article synthesizes RFC 6749 (OAuth 2.0), RFC 7591 (Dynamic Client Registration), the OpenID Connect Core 1.0 specification, and public documentation from Google, GitHub, Stripe, Supabase, and Clerk. How we research.
OAuth is what happens behind every “Sign in with Google” button, every “Connect your Stripe account” flow, every “Allow this app to read your GitHub repositories” consent screen. It is the protocol that turned third-party integrations from a security nightmare — users handing their password to whatever app asked for it — into a controlled delegation pattern where the user, not the app, decides what access to grant. For solo SaaS founders it is one of the small handful of protocols that show up at the boundary of almost every integration you will ever build.
The first thing to understand is that OAuth’s name is doing a lot of work. The protocol started life as an authorization framework: it answers the question “is this application allowed to access this resource?” It does not, on its own, answer the question “who is the human behind this request?” That second question is the job of OpenID Connect, an identity layer built on top of OAuth 2.0. When people casually say “we use OAuth for login,” what they almost always mean is “we use OpenID Connect, which is built on OAuth 2.0.” The two are intertwined in practice and worth disentangling once before going further.
The cleanest way to hold the two concepts is to map each to the question it answers:
OIDC was specified by the OpenID Foundation in 2014 precisely because everyone was already using OAuth 2.0 for login, badly, in incompatible ways. The OpenID Connect Core 1.0 spec standardized the missing identity layer: a defined id_token shape, a userinfo endpoint, a set of standard scopes (openid, profile, email), and a discovery mechanism. Today, when a SaaS product offers “Sign in with Google,” “Sign in with Microsoft,” or “Sign in with Apple,” that flow is OIDC under the hood. Pure OAuth 2.0 is reserved for situations where the application needs to call APIs on the user’s behalf without needing to know who they are — rare in practice for consumer login flows, common for marketplace integrations.
The practical takeaway: if you are building login, you want OIDC. If you are building a Slack-app-style integration that posts messages on the user’s behalf, you want OAuth 2.0 (and may not need an ID token at all). Most modern auth providers ship both in one flow, so the distinction matters mostly when you are debugging or making architectural decisions.
Every OAuth flow involves four roles, named explicitly in RFC 6749:
The flow is the choreography between these four. The client redirects the resource owner to the authorization server. The authorization server authenticates the user, asks them to consent to the scopes the client requested, and (on approval) issues a credential that the client can exchange for an access token. The client uses the access token to call the resource server’s API. The user is never asked to give their password to the client — they only ever type it into the authorization server, which they (in theory) already trust.
OAuth 2.0 defines several flows, called grant types, for different application shapes. Two of them are deprecated; four are in active use.
The recommended default for almost every modern application — web apps, mobile apps, single-page apps, and CLIs that can open a browser. The flow:
code_verifier and derives a code_challenge from it (a SHA-256 hash, base64url-encoded)./authorize endpoint with the code_challenge, the requested scopes, and a redirect_uri.redirect_uri with an authorization code.code plus the original code_verifier to the /token endpoint.access_token, an optional refresh_token, and (in OIDC flows) an id_token.PKCE (Proof Key for Code Exchange, pronounced “pixie”) was originally designed for mobile apps that could not securely store a client secret, but is now recommended for all clients including server-side web apps. It defends against a class of attacks where an interceptor steals the authorization code from the redirect.
Machine-to-machine authorization. No human user is involved — the client authenticates itself directly to the authorization server using its client_id and client_secret, and receives an access token. Used for service-to-service API calls, scheduled jobs, and any context where the credential represents the application itself rather than a user. Stripe’s API keys behave conceptually like client-credentials tokens, though Stripe does not require the full OAuth dance.
Not a flow on its own — a mechanism to mint new access tokens after the original one expires. Access tokens are short-lived (typically one hour). When the access token expires, the client sends the long-lived refresh_token to the /token endpoint and receives a fresh access token without prompting the user again. Refresh tokens may themselves expire or be rotated on use; more on that in the security section.
Designed for clients that cannot easily open a browser — smart TVs, CLI tools, IoT devices, anything where typing is hard. The device displays a short code and a URL; the user opens the URL on a phone or laptop, types the code, and consents. The device polls the authorization server until consent is granted, then receives an access token. The GitHub CLI, the AWS CLI, and most TV streaming apps use this flow.
Three credential types show up in a typical OIDC flow, and they have different purposes, lifetimes, and risks.
access_token — the credential the client presents to the resource server when calling an API. Format is provider-specific: sometimes opaque (a random string the resource server validates by calling the authorization server), sometimes a JWT (self-contained, signature-verifiable). Short-lived: one hour is the modern norm. Treat as a bearer token — anyone who has it can use it until it expires.refresh_token — the long-lived credential used to get new access tokens. Should be stored more carefully than an access token; never exposed to the browser or a non-trusted device. Some providers rotate refresh tokens on every use (the previous token is invalidated when a new one is issued).id_token — OIDC only. A signed JWT containing identity claims about the user: sub (the stable subject identifier), email, email_verified, name, picture, and provider-specific claims. The client validates the signature using the provider’s public keys, then trusts the claims. Never sent to a resource server — it is for the client’s own consumption.The deeper mechanics of JWT validation, claim verification, and signature checks are covered in what is a JWT. The OAuth spec is largely silent on the access-token format on purpose — whether the token is opaque or a JWT is left to the provider.
Scopes are the strings that name what the client is asking permission to do. They appear in the initial authorization request (scope=openid email profile) and on the consent screen the user sees. Three categories show up in practice:
openid (required to trigger an OIDC flow at all), profile, email, address, phone, offline_access. These are defined in the OIDC Core spec and behave consistently across providers.https://www.googleapis.com/auth/drive.readonly. GitHub’s repository read scope is repo. Slack’s channel-read scope is channels:read. Each provider publishes a catalog.https://www.googleapis.com/auth/drive grants full Drive access.The cardinal rule of scopes: request the minimum your product actually needs. A consent screen that asks for full inbox read access when the product only needs to send mail will get refused, gets your client app flagged in provider review, and is a security liability if your refresh token is ever stolen. Ask for read-only scopes when reads are enough; ask for write scopes only on the actions that need them.
Most consumer-facing OAuth in 2026 routes through a handful of identity providers, all of which speak OIDC:
On the integration side — where your SaaS is the client and a third party is the resource server — OAuth 2.0 (without OIDC) is the standard pattern. Stripe Connect uses OAuth so platforms can act on connected accounts’ behalf. Slack’s entire app ecosystem is built on OAuth. Google Workspace add-ons, GitHub Apps, Notion integrations, and HubSpot connections all use OAuth 2.0 with provider-specific scopes.
The protocol is well-specified; the implementations of it are where the bugs live. The same handful of mistakes show up over and over in audits and incident reports.
The email claim in an ID token is whatever the user set on the identity provider, which may or may not have been verified. The email_verified claim — a boolean — is what tells you whether the provider actually confirmed the user controls that email. A client that links accounts by email without checking email_verified: true can be tricked: an attacker creates a Google account with a victim’s email address, fails verification, and uses the unverified token to take over the victim’s account on a SaaS that did not check the claim. Always require email_verified before account linking.
An access token in localStorage is reachable by any JavaScript running on the page — including any compromised third-party script. A single XSS bug exfiltrates every token. The safer pattern is an HttpOnly, Secure, SameSite cookie issued by your own backend, with the OAuth tokens kept server-side. This is one of the main reasons modern SaaS apps run their OAuth flows through a server-side endpoint rather than a single-page-app-only flow.
Mobile apps and SPAs cannot keep a client secret secret — the binary or bundle is on the user’s device. PKCE was created specifically to protect these public clients. A flow that omits PKCE on a public client is vulnerable to authorization-code interception. The remediation is one line of code; the absence of it is a CVE waiting to happen.
The authorization server only redirects to URIs that match the client’s registered list. If your registration permits a wildcard (https://*.yourdomain.com/callback) or you accept query-string-controlled redirects on your side, an attacker can craft a flow that delivers the authorization code to a server they control. Always register exact, fully-qualified redirect_uri values; never trust a redirect target derived from user input.
Refresh tokens are long-lived, often months or years. A leaked refresh token is a persistent backdoor. Modern implementations rotate the refresh token on every use: when the client exchanges a refresh token for a new access token, the response includes a new refresh token and the old one is invalidated. If a stolen refresh token is used by an attacker, the next legitimate use by the real client fails — a detectable signal that something is wrong. Stripe rotates refresh tokens in its Connect flow; Supabase does too in its auth refresh handling. If you operate your own authorization server, rotation should be the default.
SAML (Security Assertion Markup Language) is the older enterprise SSO protocol. The differences in shape:
The pragmatic stance for solo SaaS: build with OIDC for everything you can, add SAML only when an enterprise customer specifically demands it. Most auth-as-a-service vendors (Clerk, WorkOS, Auth0) ship SAML behind a flag — you get it without having to write the XML yourself. What is SSO goes deeper on when each protocol shows up.
Implementing the OAuth 2.0 spec correctly — covering PKCE, refresh rotation, redirect validation, scope normalization, ID token verification, JWKS rotation, replay protection — is non-trivial. The reasonable default for a solo SaaS founder is to use a managed auth provider and let them carry the spec complexity:
The best auth library for Next.js roundup compares these at depth, and Clerk vs Supabase Auth breaks down the cost trade-off at every scale. The how to add OAuth to your SaaS tutorial walks through wiring one of these into a working flow.
OAuth 2.0 is the authorization framework that decoupled third-party access from password sharing. OIDC, built on top of it, is the identity layer that turned the same framework into the universal “Sign in with X” pattern. The four actors and the grant types cover almost every real-world flow; Authorization Code with PKCE is the safe default for new builds. The security mistakes that bite are mundane: unverified emails, tokens in the wrong storage, missing PKCE, open redirects, refresh tokens that never rotate. Each of them is a one-line fix in code that someone forgot to write.
For solo founders, the protocol matters less than the boundary it lives at: any integration with a third-party service, any login flow with a social provider, any platform-style feature where users connect their own accounts. Pick a managed provider, trust them with the spec, and spend your attention on the parts of the product only you can build. Companion concepts show up in what is a JWT, what is SSO, what is SOC 2 compliance, and webhook security best practices — the four topics that come up in every auth-adjacent code path on a typical SaaS backend.
The stack, prompts, pricing, and mistakes to avoid — for solo founders building with AI.