img of A Technical Guide for OAuth
Security

A Technical Guide for OAuth

Jan 19, 2025
17 min read

Continuing with the authentication and authorization post series, today we will talk about the OAuth protocol, the internet open standard for access delegation.

What is OAuth?

OAuth (most commonly referring to the OAuth 2.0 framework these days) stands for “Open Authorization.” In simpler terms, it’s an open standard that allows users to grant one application permission to access their information from another service—securely and without exposing their login credentials to that requesting application.

Let’s break that down with an example. Suppose you have a Twitter account, and you find this cool third-party application that analyzes tweets. If you want that application to analyze your tweets, you’d have to give it permission to read your Twitter data. But you don’t want to hand over your Twitter password, because who knows what they might do with it? OAuth fixes that problem. Instead of giving the third-party app your password, you give it a special token (granted by Twitter) that says, “This token allows you to read my tweets, but nothing more.” That’s basically what OAuth is all about.

OAuth 2.0 has become a key piece in modern web security. Services like Google, Facebook, GitHub, Twitter, and many others support OAuth to let you sign in quickly to new apps and websites. It saves a lot of hassle for both users and developers. If you’ve ever clicked “Log in with Google” or “Log in with Facebook,” you’ve benefited from OAuth in action.

OAuth application roles

OAuth Roles To understand OAuth, it helps to identify the various “roles” or “players” involved. Think of OAuth like a mini-ecosystem of participants. Typically, the four main roles in OAuth 2.0 are:

  1. Resource Owner: This is you, the user. You own the data or resource that an application wants to access. You are the one who ultimately decides whether or not to allow a particular application to use your data.
  2. Resource Server: This is where the user’s data lives. For instance, Twitter’s servers hold your Twitter data (tweets, profile information, etc.). When a third-party app asks for your tweets, the resource server is the place they’ll come from.
  3. Client: This is the application that needs access to the resource owner’s data. It’s called the “client” because it’s a client of the resource server. In the Twitter example, this is the third-party tweet analysis tool. It can’t access your data unless you say it can.
  4. Authorization Server: This server issues tokens to the client after the resource owner (you) approves access. Often, the authorization server is the same as the resource server, but it could also be a separate entity. In many cases, for big platforms like Google, the authorization server is a dedicated service that handles logins and token issuance.

Remember their names and what they do, it will be important later!

The Authorization Code

The Authorization Code is a concept that pops up in one of the most common OAuth flows, typically known as the “Authorization Code Flow.” The idea is pretty straightforward: The client doesn’t immediately get an access token. Instead, after the user says “yes” to granting access, the client receives a short-lived, single-use “authorization code.”

This code is basically a placeholder. Think of it as a secret message that the client can exchange with the authorization server for a real access token. That means the client can keep its credentials more secure because it doesn’t handle an access token directly in the browser where it could be exposed. Instead, it processes an authorization code on a secure server.

For example, you’re on a web page of some third-party app that wants to access your Google Calendar. You get redirected to Google to log in and confirm that you want to allow this access. Google then redirects you back to that third-party app with something called the “authorization code.” Once your app’s backend server receives that code, it contacts Google behind the scenes (securely, using the client secret) to exchange that code for an access token. This approach drastically reduces the risk of leaking sensitive tokens.

Authorization Scopes

“Scopes” are like boundaries or permission levels. They define exactly what parts of your data the client application can access. For example, if you’re granting an application permission to read your Google Calendar events, there might be a scope such as calendar.readonly that specifically says, “This application can read (but not write or delete) your calendar events.”

When the client app requests access, it must specify which scopes it needs. As the user, you’ll typically see these scopes described on the permission screen. So you might read something like, “This app wants to view your email address and basic profile info,” or “This app wants to post to your timeline.” You can then decide if you’re comfortable with that. If not, you can decline or you can sometimes limit the scopes requested (depending on the system).

Scopes are super important because they reduce the “blast radius” if something goes wrong. If an app only has permission to read your email address, it can’t suddenly start messing with your entire Google account or post on your behalf. By limiting access to only what’s needed, OAuth helps keep your data safer.

OAuth Endpoints

To make the use of the OAuth standard possible, the Client application and the Authorization Server must implement a set of endpoints that allow the exchange of tokens and the authorization process.

1. Authorization Endpoint

This is where the resource owner (you) is sent to authenticate and give permission to the client. In your browser, you’ll often see a URL like https://accounts.google.com/o/oauth2/v2/auth?... which is Google’s authorization endpoint. Once you’re there, you either log in or, if you’re already logged in, you see a prompt saying something like, “Do you allow this app to access your info?” If you say yes, the process continues; if you say no, it stops.

This endpoint needs to be able to handle the following parameters:

Query ParamDescription
response_typeDefines the type of response (“token” or “code”).
client_idUnique identifier of the client application.
redirect_uriWhere the user will be redirected after authorizing.
scopeThe requested access scopes.
stateA value used to identify a correspondence between the authorization request and response and thus prevent CSRF attacks.

2. Redirection Endpoint

After you approve or deny, you get sent back to the client’s specified URL, known as the “redirect URI” or “callback.” This must be a pre-registered, trusted URL so that malicious sites can’t hijack the process. The redirect endpoint is typically where the client receives either the authorization code or an access token, depending on the OAuth flow being used. It’s basically the finishing line for the “user-facing” part of the authorization sequence.

This endpoint needs to be able to handle the following parameters:

Query ParamDescription
codeThe authorization code received from the Authorization Endpoint.
stateThe same state value sent in the Authorization Endpoint.

3. Token Endpoint

This endpoint is usually hidden from the user and is used by the client to exchange the authorization code for an access token (and possibly a refresh token), or sometimes to directly request an access token if we’re using other OAuth flows. In a typical scenario, the client’s server will send a secure HTTPS request to something like https://accounts.google.com/o/oauth2/token with the authorization code, the client ID, the client secret, and a few other details. If everything checks out, the authorization server responds with an access token (and maybe a refresh token). This endpoint is not typically visited by the user in a browser but rather called by the client’s backend.

The client must send the following parameters to this endpoint:

Query ParamDescription
grant_typeThe type of grant being used. Can be “authorization_code”, “client_credentials” or “token”
codeThe authorization code received from the Authorization Endpoint.
redirect_uriThe same redirect URI used in the Authorization Endpoint (it is required only if it was used in the Authorization Endpoint).

The client registration

Before a client application can use the OAuth protocol, it must be registered with the Authorization Server. This registration process allows the Authorization Server to establish a trust relationship with the client. During the registration process, the client application provides relevant information such as:

  • Application Name: User-friendly name of the application.
  • Client type: The type of client application (confidential or public).
    • Confidential: Clients that can store their own credentials securely.
    • Public: Clients that cannot store their credentials securely, such as web or mobile applications.
  • Redirect URIs: The URIs where the user will be redirected after the authorization process and where the authorization code will be sent.

After the registration process, the client application receives a set of client credentials that it can use to authenticate itself with the Authorization Server. These credentials are composed of:

Client ID: A unique identifier for the client application.
Client Secret: A secret key that the client application uses to authenticate itself with the Authorization Server.

What are OAuth Flows?

OAuth 2.0 has several different “flows,” each suited to particular scenarios. An OAuth “flow” is essentially a step-by-step process that outlines how the user (resource owner), the client (application), and the authorization server interact to produce (or not produce) an access token.

Why multiple flows? Because different types of applications have different constraints. For instance, a web application with a secure backend can store a client secret safely, while a JavaScript single-page app (SPA) can’t hide its secrets as effectively. A mobile app also deals with different security challenges. OAuth 2.0 tries to cater to these diverse needs with various flows.

There are four of them: Authorization Code, Implicit Grant, Client Credentials, and Resource Owner Password Credentials.

Each flow is designed to handle security in the best way possible for its environment. Up next, I’ll discuss each of these flows in a bit more depth, starting with the most common one: the Authorization Code Flow.

OAuth Flow: Authorization Code

The Authorization Code flow is the most commonly used OAuth flow. It is designed to be used by applications that need to act on behalf of the user.

As an example of this flow, let’s consider the integration between Github and Netlify. Netlify allows you to deploy static sites from source code hosted in Github repositories. For this to be possible, the user must allow a third-party application (in this case, Netlify) to access certain resources that only the user who owns the Github account would have, such as reading their own repositories. The Authorization Code flow was created to enable this controlled access mechanism for entities that are not the user themselves.

Authorization Code Flow

Step 1: Authorization Request

The client application redirects the user to the Authorization Endpoint provided by the Authorization Server sending the following parameters:

response_type: must be “code”
client_id: The client ID received during the registration process.
redirect_uri: The URI where the user will be redirected after authorizing.
scope: The requested access scopes.

HTTP
GET https://github.com/login/oauth/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https://www.netlify.com/oauth/callback HTTP/1.1

Step 2: Issuing the Authorization Code

If the resource owner (user) authorizes the client application to consume the resources protected by the selected scopes, the Authorization Server will redirect the user to the client’s redirection endpoint, sending the authorization code as a query parameter.

HTTP
HTTP/1.1 302 Found
Location: https://www.netlify.com/oauth/callback?code=SplxlOBeZQQYbYS6WxSbIA

Step 3: Exchanging the Authorization Code for an Access Token

After receiving the authorization code, the client application can use it to request an access token from the Authorization Server. This is done by making a request to the Token Endpoint, sending the following parameters in the “application/x-www-form-urlencoded” format:

grant_type: must be “authorization_code”
code: The authorization code received in the previous step.
redirect_uri: Required only if it was used in the Authorization Endpoint.


HTTP Request
POST /login/oauth/access_token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded
     grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
     &redirect_uri=https://www.netlify.com/oauth/callback

HTTP Response
HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"bearer",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
     }

OAuth Flow: Implicit Grant

The Implicit Grant Flow is kind of like the “lightweight” version of the Authorization Code Flow, and it was historically used by single-page applications (SPAs) that run entirely in a browser without a dedicated backend server.

Here’s why it existed: In a pure JavaScript application, it’s tricky to store a client secret without exposing it to everyone (because it’s all in the browser’s code). Furthermore, making a server-side request for an access token also isn’t as straightforward. So the idea was that after the user grants permission, the authorization server directly provides an access token in the redirect URL.

The steps are similar to the Authorization Code Flow, but the difference is:

  • No authorization code is involved. After the user consents, the authorization server sends the user back to the client with the access token already in the fragment (#) part of the URL.
  • Because there’s no token exchange on a secure backend, it’s potentially less secure. The token could be exposed in the browser’s history or to malicious scripts if not handled correctly.

Implicit Grant Flow

Step 1: Authorization Request

First, the client application redirects the user to the Authorization Endpoint provided by the Authorization Server, sending the following parameters:

response_type: must be “token”
client_id: The client ID received during the registration process.
redirect_uri: The URI where the user will be redirected after authorizing.
scope: The requested access scopes.

HTTP
GET https://github.com/login/oauth/authorize?response_type=token&client_id=s6BhdRkqt3&redirect_uri=https://www.netlify.com/oauth/callback HTTP/1.1

Step 2: Issuing the Access Token

If the resource owner (user) authorizes the client application to consume the resources protected by the selected scopes, the Authorization Server will redirect the user to the client’s redirection endpoint, sending the access token as a query parameter. As the authorization code is not used in this flow, the access token is exposed to the user-agent in the redirect URL.

HTTP
HTTP/1.1 302 Found
Location: https://www.netlify.com/oauth/callback?access_token=2YotnFZFEjr1zCsicMWpAA&token_type=bearer&expires_in=3600

In many modern applications, security experts have moved away from the Implicit Flow in favor of something called the “Authorization Code Flow with PKCE” (Proof Key for Code Exchange), which is a more secure approach that can still work for mobile and SPA apps. Nonetheless, the Implicit Flow is part of the OAuth 2.0 specification and is still encountered in legacy implementations.

OAuth Flow: Client Credentials

Sometimes an application needs access to a resource or API, but there is no user. It’s just a server (the client) communicating with another server (the resource server). For instance, you might have a background service that processes some data from a third-party API regularly without any direct human involvement. That’s where the Client Credentials Flow comes in.

In this flow, we can think of the client application as the owner of the resources it’s trying to access. The client (which is typically a “confidential” client, meaning it can store secrets safely) just presents its credentials (client ID and client secret) directly to the authorization server in exchange for an access token.

Client Credentials Flow

In this flow, the client application can go directly to the Token Endpoint to obtain its access token, just by using its client_id and client_secret. To do this, it must make a request to the Token Endpoint sending the following parameters in the “application/x-www-form-urlencoded” format:

grant_type: must be “client_credentials”
scope: a list of scopes that the client application wants to access.

Single Step: Access Token Request

In addition to informing the grant type and scopes, the client must send its client_id and client_secret in the Authorization header using the Basic authentication scheme.

HTTP Request
POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded
     grant_type=client_credentials

HTTP Response
HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"bearer",
       "expires_in":3600,
     }

OAuth Flow: Resource Owner Password Credentials

The Resource Owner Password Credentials (ROPC) flow is sometimes referred to as the “password” flow. In it, the user gives the client their actual username and password, which the client then sends directly to the authorization server. The server then provides an access token if the credentials are valid.

Immediately, you might sense a red flag: “Isn’t one of the main points of OAuth to avoid giving your username and password to third-party apps?” Yes, exactly! That’s why the ROPC flow is strongly discouraged in most scenarios. It basically goes against the principle that the client should never see the user’s raw credentials. However, it’s included in the specification because there are some edge cases where it’s useful—such as a trusted first-party application, or for legacy cases where other flows aren’t available.

Resource Owner Password Credentials Flow

Single Step: Access Token Request

As the client application already has the user’s credentials, it can go directly to the Token Endpoint to obtain the access token. The request must be made sending the following parameters in the “application/x-www-form-urlencoded” format:

grant_type: must be “password”
username: The user’s username.
password: The user’s password.
scope: a list of scopes that the client application wants to access.


HTTP Request
     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=password&username=johndoe&password=A3ddj3w

HTTP Response
HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
    }

Because the client sees the user’s password, you really have to trust the client. If the client is a first-party app (like the official mobile app for a service), maybe that’s acceptable. But for third-party apps, it’s usually not.

Final Thoughts

OAuth may seem a bit wordy, with its flows and tokens and endpoints. But from a user’s perspective, it’s a life-saver: no more remembering dozens of passwords, no more worrying that a random third-party site might be storing your credentials unsafely. For developers, it’s a flexible, secure system that can integrate with just about any major service on the web.

If you remember only a few things from this article, let them be:

  1. OAuth is about delegation: letting apps access resources without giving away your password.
  2. It has multiple flows for different scenarios—choose wisely based on your type of application.
  3. Keep your secrets secret (like client secrets) and handle tokens securely.
  4. Scopes define what kind of data an app can access and are crucial for protecting user privacy.
  5. Endpoints are critical junctures in the OAuth conversation—Authorization Endpoint for user consent, Token Endpoint for exchanging codes for tokens, and a valid Redirect Endpoint to receive responses.

© 2025 Programmer Codex

GitHub