OAuth 2.0 flows explained: which one, and why the others are wrong
OAuth 2.0 flows explained: which flow suits user-facing apps and machine calls, which are deprecated, and where refresh tokens break.
On this page
- What we'll cover
- Key takeaways
- What OAuth is and is not
- The four parties
- Authorization code with PKCE
- Client credentials, for machines
- The flows not to use
- Refresh tokens, where integrations break
- Scopes and consent
- What to do before you write any code
- Connecting with CodeWords
- What to get right first
- Frequently asked questions
- Related reading
Use the authorization code flow with PKCE (Proof Key for Code Exchange) for user-facing applications, and the client credentials flow for machine-to-machine calls. OAuth 2.0 exists so an application can act on a person's data in another system without that person handing over their password. Most confusion comes from treating OAuth as an authentication framework, which it is not.
The practical version is shorter than the specification suggests. Two flows cover nearly every case, and several older flows remain only for historical reasons and are discouraged.
What we'll cover
Key takeaways
- Use authorization code with PKCE for user-facing apps and client credentials for service-to-service calls.
- An access token shows that an application may act, not who the user is, so use OpenID Connect for sign-in.
- The IETF's 2025 security guidance says the resource owner password credentials grant must not be used and the implicit grant should not be used (IETF RFC 9700, 2025).
- Most integrations that fail after a few weeks fail at refresh token handling, so store rotated tokens immediately and refresh one at a time.
- Request the minimum scopes and ask for more only when a feature needs them.
What OAuth is and is not
OAuth is delegated authorisation. A user grants your application limited access to their data somewhere else.
OAuth is not authentication. An access token says an application may do something. It says nothing reliable about who the user is. Using a token as proof of identity is a vulnerability, not a style mistake.
OpenID Connect is the authentication layer built on top of OAuth. It adds an identity token with claims about the user. Use it for "sign in with" buttons.
OAuth is not a login system for your own application, unless you use it to accept identity from an external provider.
The distinction matters in practice. An access token can be obtained for a different application and presented to yours, which is why using a token as identity fails.
The four parties
OAuth involves four parties, and the specification's vocabulary is where people get lost.
The resource owner is the person whose data it is.
The client is your application, asking for access.
The authorization server issues tokens after the person consents.
The resource server holds the data and accepts the token.
The authorization server and resource server are often the same product but they are separate roles, which is why the documentation distinguishes them.
Authorization code with PKCE
Authorization code with PKCE is the flow to use for anything user-facing, and it is the answer in most cases. It works in six steps.
The user is redirected to the provider, sees what is being requested, and approves.
The provider redirects back with a short-lived code, not a token.
Your application exchanges the code for tokens in a direct server-to-server call, so the token never passes through the browser's address bar.
PKCE binds the exchange to the original request. The client generates a secret, sends a hash of it when starting, and presents the original when exchanging. An intercepted code cannot then be redeemed by anyone else.
PKCE is required for public clients and recommended for confidential clients. The IETF security guidance says public clients must use PKCE and recommends it for confidential clients too (IETF RFC 9700, 2025). Older guidance that limits PKCE to mobile and single-page applications is out of date.
Validate the state parameter on return. This prevents a cross-site request forgery against the callback.
Client credentials, for machines
The client credentials flow lets your application authenticate as itself, with no user involved.
Use it for service-to-service calls where the data belongs to your organisation and not to a person.
It is a machine credential with a token exchange in front of it. The security benefit over a long-lived API key is that the resulting token is short-lived, which limits the value of an intercepted token.
There is no refresh token. You request a new token when the current one expires, which removes a category of the bugs described below.
Scope the token narrowly. A client credentials token can carry broad organisational access by default.
The flows not to use
Do not use the implicit flow or the resource owner password credentials flow, because older tutorials still teach both.
The implicit flow returned tokens directly in the browser redirect, which exposed them in the address bar and in history. The IETF security guidance says clients should not use it (IETF RFC 9700, 2025). Use authorization code with PKCE instead.
The resource owner password credentials flow has the user give your application their password, which defeats the purpose of OAuth. The same guidance says it must not be used (IETF RFC 9700, 2025). It survives in legacy integrations and should not be chosen for anything new.
The device flow is not deprecated. It is for input-constrained devices where the user authorises on a second device. Use it in that situation.
If a provider offers only a deprecated flow, that is a fact about their API, not permission to choose it. Raise it with them.
Refresh tokens, where integrations break
Refresh token handling is where integrations break, and most "the integration stopped working after a few weeks" reports end here.
The access token is short-lived. The refresh token is the long-lived secret and the one that needs protecting.
Many providers rotate refresh tokens. Each use issues a new token and invalidates the old one, so persist the new value immediately. The classic error is storing it in configuration the process cannot write to.
Refresh tokens can expire if unused. A monthly job whose refresh token has an inactivity limit breaks after a quiet period, and nothing warns you. Check each provider's documented lifetimes.
Concurrent refreshes invalidate each other. If two instances refresh at the same time, one wins and the other's token is dead. The failure is intermittent and hard to reproduce.
Consent can be revoked by the user or an administrator at any time. A revoked grant looks like an expired token but needs a different fix: reconnect, do not retry.
Alert on refresh failure specifically. The remedy usually needs a person to re-authorise, and nobody will know to do that from a generic error.
Scopes and consent
Request the minimum scopes, and ask for more only when a feature needs them.
Request the minimum. A consent screen that asks for broad access lowers the share of users who complete it, and a compromised token with broad scope causes a larger incident.
Request incrementally where the provider supports it. Ask for extra scopes when the feature that needs them is first used, not all at once at sign-up.
Scopes are coarse. They limit categories of access, not individual records. A scope does not replace checking that this user may touch this object.
Explain the request. Add a short sentence in your own interface before the redirect that says why you need what you ask for.
Handle partial grants. Some providers let a user approve some scopes and decline others. An application that assumes all-or-nothing will fail in confusing ways.
Re-check on change. Adding a scope to your application does not grant it for users who already authorised. They must consent again.
What to do before you write any code
Before you write code, clear the configuration work, because it causes half the friction in an OAuth integration.
Register the application and record the client identifier and secret somewhere the deployment can read.
Register every redirect URI exactly. Most providers match them strictly, and a trailing slash or a missing port turns a working flow into a generic error.
Register the local development URI too, or nobody can develop against the integration.
Check whether the provider requires a review for the scopes you want. Sensitive scopes can need an application review with its own lead time, which can be longer than the build.
Find out whether sandbox and production are separate, with separate credentials and separate consent. Many providers separate them.
Read the provider's token lifetimes, including whether refresh tokens rotate and whether they expire when unused. Those two facts determine most of your error handling.
Connecting with CodeWords
CodeWords connects to 3,000+ integrations (CodeWords integrations directory, 2026), and the authorisation, token storage and refresh handling are part of the connection, so you do not implement them per automation.
- Open CodeWords and start a new automation.
- Describe what should happen in plain language to Cody, the automation builder.
- Authorize the connection to each system, granting the narrowest access the workflow needs.
- Describe the exceptions: an expired credential, a revoked authorisation, a permission the automation lacks.
You describe the outcome; Cody builds it, connects it, and deploys it. The Free plan covers light use. Pro costs $39 per month and Business costs $100 per month as usage grows (CodeWords pricing page, 2026); details are on the pricing page.
What to get right first
Get refresh token handling right first: store the token somewhere writable, update it immediately when it rotates, and allow only one refresh at a time. OAuth integrations most often fail here, and the failure appears weeks after everything worked in testing.
Two habits make the difference. Exercise the refresh path deliberately during development instead of waiting for the access token to expire, because a flow that is never tested is never correct. And alert separately on a revoked authorisation, because it needs a person to reconnect and retrying will not fix it.
Frequently asked questions
Which OAuth flow should I use?
Use authorization code with PKCE for anything user-facing, and client credentials for service-to-service calls where no user is involved. Those two flows cover nearly every real case. Use the device flow only for input-constrained devices, and avoid the implicit and password flows for anything new.
Can I use an access token to identify the user?
No, an access token does not identify the user. It says an application may do something, not who anyone is, and a token issued for a different application can be presented to yours. Use OpenID Connect, which adds an identity token with claims about the user for exactly this purpose.
Why does my integration stop working after a few weeks?
Refresh token handling is the usual cause. Many providers rotate refresh tokens, and the new value must be stored immediately in a place the process can write to. Refresh tokens can also expire if unused, which breaks monthly jobs after a quiet period. Check each provider's documented token lifetimes.
What is PKCE and do I need it?
PKCE (Proof Key for Code Exchange) binds the token exchange to the original authorisation request, so an intercepted code cannot be redeemed by anyone else. The IETF requires it for public clients and recommends it for confidential clients (IETF RFC 9700, 2025), so yes, use it for every authorization code flow.
Why does the connection fail intermittently?
The usual cause is two instances refreshing at once: one succeeds and invalidates the other's token. Serialise refreshes so only one runs at a time, or re-read the stored token before giving up on a failure. Intermittent failures are hard to reproduce, so log each refresh attempt.
Should I request all scopes at sign-up?
No, request the minimum scopes at sign-up. Ask for more when the feature that needs them is first used. Broad consent screens lower completion rates, and adding a scope later requires users who already authorised to consent again, so request only what you need now.
The provider only offers the password flow, what now?
The provider's choice is a fact about their API, not a recommendation. The IETF says this flow must not be used (IETF RFC 9700, 2025). If you have no alternative, isolate it carefully and raise it with the provider, because providers are removing these flows and finding out during a deprecation is worse than asking now.
Why does the redirect fail with a vague error?
The usual cause is a redirect URI that does not match the registered one. Providers compare the strings exactly, so a trailing slash, a different port, or http instead of https all fail. The error is often vague on purpose for security reasons. Compare the registered and requested URIs character by character.
What should I check before building?
Registered redirect URIs including your local one, whether your scopes need provider review, whether sandbox and production are separate, and the token lifetimes, particularly whether refresh tokens rotate and whether they expire when unused.
How do I tell an expired token from a revoked authorisation?
The responses usually differ, and the distinction matters because only one is fixable by retrying. A revoked authorisation needs a person to reconnect, so it deserves its own alert rather than being folded into generic authentication failures.