Skip to content

Integrate ​

With Backend Web Services ​

When you start the AuthC server a secret key is generated on SERVER_KEY_PATH=./keyfile in the JSON Web Key format. This key is used to verify that JWT tokens recieved by your web services/backend APIs are verified and trusted.

JWTs are frequently passed from your frontend application to your web service in order to authenticate users into your APIs.

Described below is an example key and how you can use this key proided by AuthC in your web services.

Generate Key ​

The example key below is generated by AuthC on first start (or uses an existing keyfile if one is already available). The AuthC Serer uses this key to SIGN JWTs that are made available for your application APIs - you'll be using this key to VERIFY the JWT passed from AuthC to authenticate a user with your APIs.

Both the key on the AuthC server and installed on your web service must match, even if they are hosted on different servers. This requires that your web service references the key produced by AuthC either through an environment variable or file.

javascript
// example ./keyfile in JWK located in AuthCompanion code directory
{
  "key_ops": [
    "sign",
    "verify"
  ],
  "ext": true,
  "kty": "oct",
  "k": "EClK3wR3GxRM2sIAfZE00IGkpM_HeTVdy_H93_EqDcddonfEMWQpSrtGju_xFC9vGUAzHAcREbUe1XeodmTZ2Q",
  "alg": "HS256"
}

Import Key & Verify Tokens ​

Next, import the JWK into your web service. One way to do this is by reading a file like below (make a copy of the file generated by AuthC on your server).

javascript
// ./key.js
//  This function reads a key from a file called "keyfile" and imports it using the Web Cryptography API.
import { readFileSync } from "fs";
import { webcrypto } from "crypto";

const { subtle } = webcrypto;

export async function importKey() {
  try {
    const rawKey = readFileSync("./keyfile");
    const importkey = JSON.parse(rawKey);

    const key = await subtle.importKey(
      "jwk",
      importkey,
      {
        name: "HMAC",
        hash: "SHA-256",
      },
      true,
      ["sign", "verify"]
    );
    return key;
  } catch (error) {
    console.log(error);
  }
}

With the key imported, you can use a library like https://github.com/panva/jose to both verify incoming JWTs (using the secret key) and access the payload contents to learn more about the user.

javascript
// jwt.js
import * as jose from "jose";
import { importKey } from "./key.js";

export async function validateJWT(jwt) {
  try {
    // Load the key like below or import the key earlier into memory
    const secretKey = await importKey();

    const { payload } = await jose.jwtVerify(jwt, secretKey);

    return payload;
  } catch (error) {
    throw {
      statusCode: 401,
      message: "Unauthorized, Token Validation Failed.",
    };
  }
}

Passing this verification step allows the request to access the API resources - any errors should deny the request to your APIs.

With Frontend Clients ​

After a user's successful account registration or login, AuthC will first save the user's JWT in local storage and Refresh token as a cookie, then redirect the user to your frontend application.

Let AuthC know where to redirect a user by setting the config APPLICATION_ORIGIN=http://localhost:3002/v1/web/home - this value should typically your web application's home page.

With the access token easily accessible, your web application can now:

  • Check if a user is logged in by seeing if the JWT variable is set. If it isn't, redirect the user to the login page.
javascript
if (!localStorage.getItem("ACCESS_TOKEN")) {
  window.location.href = "http://auth.example.com";
}
  • Retrieve the token from local storage and add it as a Bearer HTTP authentication header when calling backend APIs/services to authenticate the user.
javascript
const token = localStorage.getItem("ACCESS_TOKEN");

const response = await fetch(yourWebService, {
  method: "POST",
  headers: {
    "Content-type": "application/json",
    Authorization: `Bearer ${token}`, // notice the Bearer + AuthC's access token
  },
  body: JSON.stringify(yourData),
});

const result = await response.json();
console.log(result);
  • Decode the JWT on the client to access data in the payload - which provides more information about the user.
javascript
import jwtDecode from "https://cdn.skypack.dev/jwt-decode";

const token = localStorage.getItem("ACCESS_TOKEN");

let decodedClaims = jwtDecode(token);
console.log(decodedClaims);

/* prints:
{
  "userid": "qbnjnj3rduw8bgg7hbtgnici",
  "name": "Authy Person",
  "email": "hello@authcompanion.com",
  "userFingerprint": "$argon2id$v=19$m=64000,t=3,p=1$hLGlPgligsPoP5muWpUEqw$JaR0wRBMDlbN/jBkJbyw/C4GmcScJ3uylQRDjATZNT4",
  "scope": "user",
  "iat": 1688927980,
  "exp": 1688931580
}
 */
  • Logout a user by simply deleting the token on the client side, so that it can't be used for subsequent API calls.
javascript
function LogoutUser() {
  localStorage.removeItem("ACCESS_TOKEN");
}

Implement the "Silent Refresh" for JWTs ​

Silent refresh is a mechanism to generate a new access token using a refresh token automatically. This is true when the access token is expired but refresh token is available and valid.

AuthC has a default expiration of 1 hour for all short lived access tokens; making the Silent Refresh an important implementation step to prevent users from needlessly logging in again.

Implementing the "Silent Refresh" follows three steps:

  1. Access your web service resource with the access token set by AuthC
  2. if 401 - Unauthorized is returned by server, Get a new access token by using the Refresh token with AuthC's /auth/refresh endpoint
  3. Retry step 1 using the new access token
javascript
async function fetchResourceWithToken() {
  const token = localStorage.getItem("ACCESS_TOKEN");
  const headers = {
    "Content-type": "application/json",
    Authorization: `Bearer ${token}`,
  };

  return await fetch("/private/resource", {
    method: "POST",
    headers,
    body: JSON.stringify(yourRequestData),
  });
}

async function refreshAccessToken() {
  const refreshResponse = await fetch("http://127.0.0.1:3002/v1/auth/refresh", {
    method: "POST",
    headers: {
      "Content-type": "application/json",
    },
    body: JSON.stringify({}),
  });

  const refreshBody = await refreshResponse.json();

  if (refreshResponse.ok) {
    localStorage.setItem("ACCESS_TOKEN", refreshBody.data.attributes.access_token);
  }

  return refreshResponse.ok;
}

async function handleResourceResponse() {
  try {
    const response = await fetchResourceWithToken();
    const webserviceResponse = await response.json();

    if (response.status === 401) {
      const refreshTokenSuccess = await refreshAccessToken();

      if (refreshTokenSuccess) {
        const refreshedResponse = await fetchResourceWithToken();
        const refreshedWebserviceResponse = await refreshedResponse.json();

        // Use refreshedWebserviceResponse as needed
      } else {
        // Handle refreshToken failure
        console.error("Refresh token failed");
      }
    } else {
      // Use webserviceResponse as needed
    }
  } catch (error) {
    // Catch any errors that occur during the API calls
    console.error(error);
  }
}

// Call the function to handle API responses
handleResourceResponse();

Reference Architecture ​

Roughly, your experience will look something like this:

landscape

Be sure to familiarize yourself with token-based authentication using JSON Web Tokens.