Web Application Flow

You can enable other users to authorize your Auth0 app

You can integrate with Portal to authentication users. User authenticating with your Portal Apps will follow the flow:

Overview of the Web application flow

  1. Users are redirected to request their Portal Identity.
  2. Users are redirected back to your app by Portal.
  3. Your app exchanges the authorization code for tokens and uses them to access the API.

1. Request User Portal Identity

GET https://auth.portalgaming.com/authorize

The following parameters are used with this API.

PARAMETER NAMETYPEDESCRIPTION
redirect_uri(required)stringThe URL in your application where users will be sent after authorization. See details below about redirect urls.
audiencestringhttps://api.portalgaming.com
response_typestringIndicates which OAuth 2.0 flow you want to use. Use code for Authorization Code Grant Flow.

id_token
code
token
scopestringA space-delimited list of scopes. If not provided, scope defaults to an empty list for users that have not authorized any scopes for the application.
statestringAn unguessable random string. It is used to protect against cross-site request forgery attacks.
code_challengestringA PKCE parameter, a random secret for each request you make. This must be a string with at least 22 characters.
code_challenge_methodstringMethod used to generate the challenge. The PKCE spec defines two methods, S256 and plain, however, Portal only supports S256 since the latter is discouraged.

📘

PCKE is required for all authorization code flow requests.

2. Exchange users authorization code for a token

Providing the user accepts your request, Portal redirects back to your site with a temporary code in a code parameter as well as the state you provided in the previous step in a state parameter. The temporary code will expire after 10 minutes. If the states don't match, then a third party created the request, and you should abort the process.

POST https://auth.portalgaming.com/oauth/token
curl --location --request POST 'https://auth.portalgaming.com/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data "grant_type=authorization_code&code=$AUTH_CODE&redirect_uri=http://localhost:3001/auth/callback&code_verifier=$CODE_VERIFIER&audience=https://api.portalgaming.com&client_id=$CLIENT_ID"
const body = new URLSearchParams({
  grant_type: 'authorization_code',
  code: AUTH_CODE,
  redirect_uri: REDIRECT_URI,
  code_verifier: codeVerifier,
  audience: CLIENT_ID,
  client_id: CLIENT_ID,
});

const response = await fetch(TOKEN_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: body.toString(),
});
payload = {
  'grant_type': 'authorization_code',
  'code': auth_code,
  'redirect_uri': REDIRECT_URI,
  'code_verifier': code_verifier,
  'audience': CLIENT_ID,
  'client_id': CLIENT_ID,
}

headers = {
  'Content-Type': 'application/x-www-form-urlencoded',
}

response = requests.post(TOKEN_URL, data=urllib.parse.urlencode(payload), headers=headers)

This endpoint takes the following input parameters.

PARAMETER NAMETYPEDESCRIPTION
client_idstringThis should be the UUID of your Portal App
grant_typestringauthorization_code
codestringThe authorization code returned from the /authorize callback
redirect_uristringThe redirect_uri specified when authorizing the user
code_verifierstringOptional. When using PKCE with a public client, code_verifier is required.
audiencestringThe audience the token is valid for.

Response

By default, the response takes the following form:

{
  "access_token": "U-LAkRFclMaTyHWMlVbArD2wkpQGcUIyY-YlSjWkayF",
  "expires_in": 86400,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImYyNjJhMzIxNDIxM2QxOTRjOTI5OTFkNjczNWIxNTNiIn0.eyJzdWIiOiIwNDAwNTBjMS02M2JiLTQxMjYtOGQ2YS05NzVlZjBhYzViYWQiLCJhdF9oYXNoIjoiNjJRSzdmSnF4MVplNVIyU01xU2NPZyIsImF1ZCI6ImNjNDk3ODY0LTJkZDItNGNhOC05NTg0LTAwNzRiYTMyMWJiMSIsImV4cCI6MTcyMjAxNjI1OSwiaWF0IjoxNzIyMDEyNjU5LCJpc3MiOiJodHRwczovL3BvcnRhbC1wbGF0Zm9ybS1hcGkuZGV2LnN1cGVyZHVwZXJkZXYueHl6In0.rpI0Z_ppE7JWId1jspqgmravp-EDGsMOujdUoUPFLqcHdi7Qa4B5TbcMJsRkH7XyBx1RUV-DR05hoa65TLPqC4THvqiqeM2iIIA2GSFU4wJC8Dy8bK5aGJC3zT1haZocukbBLJYCF42VOGl_Nfct_pGr3LHeetQRztgjSHQ-oo9QZY4XpjlQW3Adx-I2vsj8mih5UblLfOznyFB41nbw4TVJvgUk7NhlkTBKuJJEILSkDlAa4hSYtoXfNmVMrxke-1W9BSADwTGUmcMK0hjYhGUiN6vzLZErJyVuP8y7eIM-62dwg1mfw3UKR2P_0IlZ0xoAEch85063IwXZVGULxVuT7zZimliwFtY4xXcQLQ7-m4jJG0tpQceTwk23lUsJ2q-4enDRdbOkf03zUpIWxxC9YK1Q2amJvL-_DA2uu-ViOUPOgcl5PMMLaBjq2jFTaR6bGCRCQ8ag4uBP8-0ZJ9LN3vUZeZ7eI5F9up_24LjU6lBPjzMwAKpGXlJnckOb",
  "scope": "openid",
  "token_type": "Bearer"
}

3. Use the access token to access the API

curl 'https://api.portalgaming.com/v1/users/me' \
--header 'Authorization: Bearer U-LAkRFclMaTyHWMlVbArD2wkpQGcUIyY-YlSjWkayF'

Response

{
  "data": {
    "id": "040050c1-63bb-4126-8d6a-975ef0ac5bad",
    "email": "[email protected]",
    "status": "Verified",
    "profilePictureUrl": null,
    "profile": {
      "isPasswordRecoverySet": true,
      "id": "75c371a5-c2d7-455e-bd44-f7aee35dcad3",
      "externalId": "pla_e4c51972-2670-4c27-a9c5-2dc11e697ffc",
    }
  },
  "status": 200,
  "error": null
}

Web Application Flow Basic Example


Prerequisites

  1. Node.js and npm: Ensure you have Node.js and npm installed.
  2. Create an OAuth Client: Register your application with your Portal Platform Identity Service to get the CLIENT_ID and CLIENT_SECRET.

Registering your Portal App

First you'll need to register your application.

Every registered OAuth app is assigned a unique Client ID and Client Secret. The client secret is used to get an access token for the signed-in user. You must include the client secret in your native application, however web applications should not leak this value.

You can fill out every other piece of information however you like, except the Authorization callback URL. This is the most important piece to securely setting up your application. It's the callback URL that GitHub returns the user to after successful authentication. Ownership of that URL is what ensures that users sign into your app, instead of leaking tokens to an attacker.

For example, we will have our development server running locally at http://localhost:3001 so we set our redirectUris to http://localhost:3001/auth/callback.

Step 1: Set Up the Project

  1. Create a new directory for your project:
mkdir portal-oauth-demo
cd portal-oauth-demo
  1. Initialize a new Node.js project:
npm init -y
  1. Install the required packages:
npm install express axios querystring dotenv

Step 2: Create Environment Variables

CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
REDIRECT_URI=http://localhost:3000/auth/callback
AUTHORIZATION_URL=https://auth.portalgaming.com/authorize
TOKEN_URL=https://auth.portalgaming.com/oauth/token

Step 3: Set Up the Express Server

const express = require("express");
const axios = require("axios");
const querystring = require("querystring");
const { randomBytes, createHash } = require("node:crypto");
require("dotenv").config();

const app = express();
const PORT = 3001;

const codeVerifiers = {};

const { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, AUTHORIZATION_URL, TOKEN_URL } =
  process.env;

function generatePKCEPair() {
  // Total of 44 characters (1 Bytes = 2 char) (standard states that: 43 chars <= verifier <= 128 chars)
  const NUM_OF_BYTES = 22;
  const HASH_ALG = "sha256";
  const randomVerifier = randomBytes(NUM_OF_BYTES).toString("hex");
  const hash = createHash(HASH_ALG).update(randomVerifier).digest("base64");
  const challenge = hash
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, ""); // Clean base64 to make it URL safe
  return { verifier: randomVerifier, challenge };
}

// Step 1: Redirect to Authorization Server
app.get("/login", async (req, res) => {
  const challenge = generatePKCEPair();

  // Store the code verifier for later use
  codeVerifiers[req.sessionID] = challenge.verifier;

  const params = querystring.stringify({
    response_type: "code",
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    scope: "openid profile",
    code_challenge: challenge.challenge,
    code_challenge_method: "S256",
  });

  res.redirect(`${AUTHORIZATION_URL}?${params}`);
});

// Step 2: Handle the Callback from the Authorization Server
app.get("/auth/callback", async (req, res) => {
  const { code, error, error_description } = req.query;
  const codeVerifier = codeVerifiers[req.sessionID];

  if (error) {
    return res.json({ error, error_description });
  }

  if (!codeVerifier) {
    return res.status(400).send("Missing code verifier");
  }

  try {
    // Step 3: Exchange Authorization Code for Tokens
    const response = await axios.post(
      TOKEN_URL,
      querystring.stringify({
        grant_type: "authorization_code",
        code,
        redirect_uri: REDIRECT_URI,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        code_verifier: codeVerifier,
      }),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      },
    );

    const { access_token, id_token } = response.data;

    // Step 4: Decode the ID Token (optional)
    const userInfo = decodeJwt(id_token);
    res.json({ access_token, id_token, userInfo });
  } catch (error) {
    res.status(500).send(error.response.data);
  } finally {
    // Clean up the code verifier
    delete codeVerifiers[req.sessionID];
  }
});

// Utility function to decode JWT (without verification)
function decodeJwt(token) {
  const [header, payload] = token
    .split(".")
    .slice(0, 2)
    .map((part) => Buffer.from(part, "base64").toString("utf8"));
  return {
    header: JSON.parse(header),
    payload: JSON.parse(payload),
  };
}

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Step 4: Run the Server

node index.js

What’s Next