What Is OAuth 2.0?

OAuth 2.0 is a standard that lets one application act on a user's behalf in another, without the user handing over their password. When a calendar app asks to “connect your Google account” and you approve a list of permissions on Google's own page, that is OAuth. The app ends up with a short-lived access token limited to what you approved, and your password never leaves Google.

Last updated . How this page is checked.

The problem OAuth solves

Before OAuth, the way to let an app read your mailbox was to give it your email password. The app then had everything: it could read, send, delete and change the password itself, and the only way to take access back was to change the password and break every other app you had connected.

OAuth replaces the password with a token that is issued for one app, limited to named permissions, short-lived, and revocable on its own. The technical term is delegated authorization: you delegate part of your access to a program. Note that the word is authorization, not authentication. OAuth answers “what may this app do?”, not “who is this person?”; a separate layer, OpenID Connect, adds the second question.

The four roles

RoleWhat it isIn the calendar example
Resource ownerThe user who owns the dataYou
ClientThe application that wants accessThe calendar app
Authorization serverSigns the user in, asks for consent, issues tokensGoogle's account service
Resource serverThe API that holds the data and accepts tokensThe Google Calendar API

The client is registered with the authorization server in advance and receives a client ID, and, if it runs on a server that can keep one, a client secret. A browser or mobile app cannot keep a secret from its own user, so it registers as a public client and has none.

The authorization code flow

This is the flow for anything with a user in front of it, and the one to learn first.

1. The app sends the browser to the authorization server.

GET https://auth.example.com/authorize
      ?response_type=code
      &client_id=calendar-app
      &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
      &scope=calendar.read
      &state=k3Vx9pQ2
      &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
      &code_challenge_method=S256

2. The user signs in and approves. This happens on the authorization server's own page. The app never sees the password.

3. The browser is sent back to the app with a one-time code.

https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=k3Vx9pQ2

4. The app exchanges the code for tokens, in a direct request from its back end that the browser never sees.

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&client_id=calendar-app
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

→ 200 OK
  { "access_token": "2YotnFZFEjr1zCsicMWpAA",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
    "scope": "calendar.read" }

5. The app calls the API with the access token.

GET /v1/events HTTP/1.1
Host: api.example.com
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA

Why there are two steps, and what PKCE adds

The code travels through the browser's address bar, where history, extensions and logs can see it. So the code is made worthless on its own: it works once, expires in about a minute, and must be exchanged in a second request that proves the caller is the app that started the flow.

That proof is PKCE, pronounced “pixy”. The app invents a random code_verifier, sends only its SHA-256 hash as the code_challenge in step 1, and reveals the verifier itself in step 4. Someone who intercepts the code does not have the verifier and cannot redeem it. PKCE was designed for mobile apps and is now recommended for every client, including those that also hold a client secret. The state value is a separate protection: the app checks that the value coming back matches the one it sent, which stops an attacker from feeding it somebody else's code.

The other flows

Flow (grant type)Use it whenUser involved
Authorization code + PKCEA web, mobile or desktop app acts for a userYes
Client credentialsA service calls an API as itself: cron jobs, back-end to back-endNo
Device authorizationThe device has no browser or keyboard: TVs, command-line toolsYes, on a second device
Refresh tokenGetting a new access token without asking the user againNo
ImplicitDo not use. Tokens were returned in the URL.—
Resource owner passwordDo not use. The app collects the user's password.—

The last two are in the original specification and are advised against by the current security best practice, RFC 9700. You will still find them in older integrations.

Client credentials is the simplest flow and the one most worth trying by hand, because it is a single request with no browser involved:

POST /token HTTP/1.1
Host: auth.example.com
Authorization: Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=reports.read

The Authorization header is the client ID and secret joined by a colon and Base64-encoded, which is ordinary HTTP Basic authentication. Note the body: token requests are form-encoded, not JSON, and sending JSON is one of the commonest reasons a first attempt fails.

Access tokens, refresh tokens and scopes

An access token is what the API accepts. It is sent as a bearer token in the Authorization header, and it is deliberately short-lived — often an hour — so that a stolen one stops working soon. OAuth does not say what a token looks like. Some are random strings the API looks up; others are JWTs the API verifies by signature, which you can inspect in the JWT decoder. OAuth vs JWT explains how the two relate.

A refresh token is longer-lived and is sent only to the authorization server, in exchange for a fresh access token when the old one expires. That is how an app stays connected for months while every token the API sees is at most an hour old.

Scopes are the permissions: space-separated strings such as calendar.read that the app requests in step 1 and the user sees on the consent screen. The token carries only the scopes that were granted. Ask for the narrowest set that does the job; users refuse broad requests, and a leaked narrow token does less harm.

OAuth is not login

An access token proves that someone authorised the app to call an API. It does not tell the app who that person is, and using it as proof of identity has caused real account-takeover bugs. OpenID Connect is a thin layer over OAuth that fills the gap: add the scope openid, and the token response also contains an id_token, a signed JWT that states who signed in, for which app, and when. “Sign in with Google” is OpenID Connect; “let this app read my calendar” is plain OAuth.

And when no user is involved and the provider simply wants to identify your application, OAuth may be more than you need: an API key does that job. The API authentication guide sets the options side by side.

Errors you will meet

WhereErrorUsual cause
Authorization pageredirect_uri mismatchThe URI sent is not character-for-character one of those registered for the client
Token endpoint, 400invalid_grantThe code was already used or has expired, the verifier does not match, or the refresh token was revoked
Token endpoint, 401invalid_clientWrong client ID or secret, or sent in the body when the server expects Basic auth
Token endpoint, 400unsupported_grant_typeA JSON body instead of form encoding, or a flow not enabled for this client
API, 401invalid_tokenThe access token has expired or was issued for a different API
API, 403insufficient_scopeThe token is valid but lacks the scope this endpoint needs

On a 401 from the API, read the WWW-Authenticate response header; it names the error and often the reason.

Trying a token request by hand

In the API tester, set the method to POST and the URL to the provider's token endpoint, choose Basic auth in the Auth tab and enter the client ID and secret, add the header Content-Type: application/x-www-form-urlencoded, and put grant_type=client_credentials in the body. Copy the access_token from the response, switch the Auth tab to Bearer token, and call the API with it. The request is relayed through this site's server, so use a test client rather than production credentials; the curl basic auth example gives the same request for your own terminal.

References

These links leave api-tester.com, which is independent of the organisations they point to.

Frequently asked questions

What is OAuth in simple terms?

A way to give an application limited access to your account on another service without telling it your password. You approve the request on the service's own page, and the application receives a token that works only for what you approved.

Is OAuth authentication or authorization?

Authorization. It decides what an application may do, not who the user is. OpenID Connect, which is built on top of OAuth 2.0, adds authentication by issuing an ID token that identifies the user.

What is the difference between OAuth and an API key?

An API key identifies an application and usually lasts until revoked. An OAuth access token represents permission a specific user granted to an application, is limited to named scopes, and expires after a short time.

What is an access token?

The credential an OAuth client sends to an API, normally in an Authorization header after the word Bearer. It is short-lived and limited to the scopes that were granted. Its format is up to the provider and may be a random string or a JWT.

What is PKCE and do I need it?

Proof Key for Code Exchange: the client sends a hash of a random value when it starts the flow and the value itself when it redeems the code, so an intercepted code cannot be used by anyone else. Current guidance recommends it for every client using the authorization code flow.

Which OAuth flow should I use?

Authorization code with PKCE when a user is present, client credentials when a service calls an API as itself, and the device flow for hardware without a browser. The implicit and password flows are no longer recommended.