Skip to content

Oauth2

Oauth2

  • Used for Authorization
    • Enables third party application access to a limited version of the service
    • "Login with Facebook"

Why is OAuth still hard in 2023?

Grant types
- Client Credentials: - Use if the Application is the Resource Owner
- Authorization Code: - If the application is a web app that is executing on the same server. Usually returns a Session token
- Resource Owner Password - If the Application can be trusted with the users credentials.
- Implicit: Used for Single Page Applications
- PKCE (Proof Key for Code Exchange): Used for native Application. Similar to Authorization Code
- OpenID Connect:
- Device Code:
- Hybrid:

Roles:
- Resource Owner: the entity that can grant access to a protected resource. Typically this is the end-user.
- Application: an application requesting access to a protected resource on behalf of the Resource Owner.
- Resource Server: the server hosting the protected resources. This is the API you want to access.
- Authorization Server: the server that authenticates the Resource Owner, and issues Access Tokens after getting proper authorization. In this case, Auth0.
- User Agent: the agent used by the Resource Owner to interact with the Application, for example a browser or a native application.

Authorization Code

  • Get a token for your email app to access your google mail.

Note

The token gets sent from google directly to the application server. Does not go through the browser

Protocol Flow:

sequenceDiagram
Application (Client) ->> User (Resource Owner): 1. Authorization Request
User (Resource Owner) ->> Application (Client): 2. Authorization Grant
Application (Client) ->> Authorization Server: 3. Authorization Grant
Authorization Server ->> Application (Client): 4. Access Token
Application (Client) ->> Resource Server: 5. Access Token
Resource Server ->> Application (Client): 6. Protected Resource

Parameters

https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml

Name Parameter Usage Location
client_id authorization request, token request
client_secret token request
response_type authorization request
redirect_uri authorization request, token request
scope authorization request, authorization response, token request, token response
state authorization request, authorization response
code authorization response, token request
error authorization response, token response
error_description authorization response, token response
error_uri authorization response, token response
grant_type token request
access_token authorization response, token response
token_type authorization response, token response
expires_in authorization response, token response
username token request
password token request
refresh_token token request, token response
nonce authorization request
display authorization request
prompt authorization request
max_age authorization request
ui_locales authorization request
claims_locales authorization request
id_token_hint authorization request
login_hint authorization request
acr_values authorization request
claims authorization request
registration authorization request
request authorization request
request_uri authorization request
id_token authorization response, access token response
session_state authorization response, access token response
assertion token request
client_assertion token request
client_assertion_type token request
code_verifier token request
code_challenge authorization request
code_challenge_method authorization request
claim_token client request, token endpoint
pct client request, token endpoint
pct authorization server response, token endpoint
rpt client request, token endpoint
ticket client request, token endpoint
upgraded authorization server response, token endpoint
vtr authorization request, token request
device_code token request
resource authorization request, token request
audience token request
requested_token_type token request
subject_token token request
subject_token_type token request
actor_token token request
actor_token_type token request
issued_token_type token response
response_mode Authorization Request
nfv_token Access Token Response
iss authorization request, authorization response
sub authorization request
aud authorization request
exp authorization request
nbf authorization request
iat authorization request
jti authorization request
ace_profile token response
nonce1 client-rs request
nonce2 rs-client response
ace_client_recipientid client-rs request
ace_server_recipientid rs-client response
req_cnf token request
rs_cnf token response
cnf token response
authorization_details authorization request, token request, token response
dpop_jkt authorization request

Attacks

Reusing Account tokens

Check that an old Code is expired after one use. Or after a short time period.

Insufficient redirect URI validation

Check the redirect URI for an attacker controlled site.
Check for a 302 Redirect on the scoped domain

CSRF

Check that State parameter
- Make sure that it is checked by the server
- Make sure that it is not a static value
- Make sure that if you remove the parameter all together that the request fails.

Otherwise it can be subject to CSRF attacks

Google OAUTH Example

Setup:
1. Go to https://console.cloud.google.com/apis/credentials and set up a new OAuth 2.0 Client IDs
2. Create Credentials -> OAuth client ID -> Web Application
3. Set Authorized JavaScript origins to http://test.generalzero.org:5000/
4. Set Authorized redirect URIs to http://test.generalzero.org:5000/oauth/google/callback
5. Set the client_id and client secret to env variables.

Flow:
1. On visit to http://test.generalzero.org:5000/ there is a link to /oauth/google
2. When the user visits /oauth/google the site returns a 302 redirect to Googles OAUTH endpoint with some parameters.
- Google Parameters {"response_type": "code", "client_id":"{OAuth_Client_ID}", "redirect_uri": "http%3A%2F%test.generalzero.org%3A5000%2Foauth%2Fgoogle%2Fcallback", "scope": "email", "state":"fb139654c956ac64dfb39e0000000000"}
- The site also sets a oauth_state cookie with the test.generalzero.org domain oauth_state=fb139654c956ac64dfb39e0000000000; HttpOnly; SameSite=Lax
3. Google Does its flow and then makes a 302 redirect back to the redirect_uri.
- Parameters: {"state":"fb139654c956ac64dfb39e0000000000", "code":"4%2F0AcvDMrDA-4AAAAAAAAAA8ffzMy4XJ0qSDFEt_1SfYAAAAxSl7YBDfFSdtALVsQL4JmYfdQ", "scope":"email+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email", "authuser":"0", "prompt":"none"
4. On the /oauth/google/callback the endpoint checks that the state paramater is the same as the set oauth_state cookie that is submitted on the request from the browser.
5. This means that you have a valid token and code to query google as the server.
6. THE SERVER makes a request to google to retrieve an access token.
- Parameters: {"grant_type":"authorization_code", "code":"4%2F0AcvDMrDA-4AAAAAAAAAA8ffzMy4XJ0qSDFEt_1SfYAAAAxSl7YBDfFSdtALVsQL4JmYfdQ", "redirect_uri":"http%3A%2F%test.generalzero.org%3A5000%2Foauth%2Fgoogle%2Fcallback", "client_id":"{OAuth_Client_ID}", "client_secret":"{OAuth_Client_SECRET}"}
7. Now the server has the access_token from google to request information about the user.
- https://www.googleapis.com/oauth2/v2/userinfo with the Header Authorization: Bearer ${access_token}

Test Code:

import express from "express";
import crypto from "crypto";

const PORT = 5000;
const CLIENT_ID = process.env.CLIENT_ID as string;
const CLIENT_SECRET = process.env.CLIENT_SECRET as string;
const DOMAIN = "http://test.generalzero.org"
const SECURE = false;

const app = express();

app.use(express.static("static"));

app.get("/oauth/google", (req, res) => {
  const params = new URLSearchParams();

  params.set("response_type", "code");
  params.set("client_id", CLIENT_ID);
  params.set("redirect_uri", `${DOMAIN}:${PORT}/oauth/google/callback`);
  params.set("scope", "email");

  const state = crypto.randomBytes(16).toString("hex");
  params.set("state", state);

  const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;

  if (SECURE){
		res.set("Set-Cookie", `oauth_state=${state}; HttpOnly; Secure; SameSite=Lax`);
  }
  else {
		res.set("Set-Cookie", `oauth_state=${state}; HttpOnly; SameSite=Lax`);
  }
  res.redirect(url);
});

app.get("/oauth/google/callback", async (req, res) => {
  const { code, state } = req.query;
  const oauthState = getCookie("oauth_state", req.headers.cookie as string);

  console.log("/oauth/google/callback")
  console.log(`req.headers.cookie: ${req.headers.cookie}`)
  console.log(`req.query: ${req.query}`)

  if (state !== oauthState) {
	return res.status(400).send("Invalid state");
  }

  const accessToken = await exchangeCodeForToken(code as string);
  const userInfo = await getUserInfo(accessToken);

  res.header("Content-Type", "application/json").send(JSON.stringify(userInfo));
});

app.listen(PORT, () => {
  console.log(`Example app listening at ${DOMAIN}:${PORT}`);
});

function getCookie(name: string, cookies: string) {
  console.log(`name:${name}, cookie: ${cookies} `)
  const cookie = cookies.split(";").find((cookie) => cookie.trim().startsWith(`${name}=`));
  if (!cookie) {
	return null;
  }
  return cookie.split("=")[1];
}

async function exchangeCodeForToken(code: string) {
  const resp = await fetch(`https://oauth2.googleapis.com/token`, {
	method: "POST",
	headers: {
	  "Content-Type": "application/x-www-form-urlencoded",
	},
	body: new URLSearchParams({
	  grant_type: "authorization_code",
	  code,
	  redirect_uri: `${DOMAIN}:${PORT}/oauth/google/callback`,
	  client_id: CLIENT_ID,
	  client_secret: CLIENT_SECRET,
	}).toString(),
  });

  if (!resp.ok) {
	throw new Error("Something went wrong");
  }

  const { access_token } = (await resp.json()) as { access_token: string };

  return access_token;
}

async function getUserInfo(accessToken: string) {
  const resp = await fetch(`https://www.googleapis.com/oauth2/v2/userinfo`, {
	headers: {
	  Authorization: `Bearer ${accessToken}`,
	},
  });

  const json = (await resp.json()) as { email: string };
  return json;
}