Skip to content

CodeCoupler Application Basics OIDC Authentication 🔒

What is this?

When you're setting up OIDC-based authentication, there's a lot to think about. For example, you've got to figure out how to store the sensitive tokens at runtime and how to add the right authentication info to requests to the resource server.

The @axa-fr/oidc-client library is a great way to outsource these problems to a service worker. All tokens are managed within the service worker and cannot be read out (by default). Every request to the resource server automatically gets the necessary authentication info, and token refresh happens fully automatically.

This library is a wrapper that makes the library even easier to use. The whole initialization phase, including detailed error handling, just disappears behind a single promise, and logins and logouts go behind simple functions. It's easy to see when a user is logging in, logging out, or changing roles with a watcher.

On top of that, this library solves another problem of OIDC authentication in connection with single page applications (SPA). OIDC always needs a redirect to the authentication server and back. But this goes against the idea of an SPA, which usually doesn't need page loads. This library solves the problem by handling the entire OIDC authentication via a popup.

Setup

Install Bundle and Dependencies:

npm i vue @axa-fr @codecoupler-pro/ui-bundle

Define Aliases, Externals and Assets:

{
  module: "vue",
  global: "Vue",
  entries: ["dist/vue.runtime.global.prod.js"]
},
{
  module: "@axa-fr/oidc-client",
  global: "oidc-client",
  entries: ["dist/index.umd.cjs"]
},
{
  module: "@codecoupler-pro/app-oidc",
  global: ["codecoupler", "app", "oidc"]
},
{
  module: "@codecoupler-pro/ui-bundle",
  global: ["codecoupler", "ui", "bundle"],
  entries: [
    "dist/codecoupler-pro-ui-bundle.js"
  ]
}

Provide Service Worker Scripts:

The library usually tries to load the files OidcServiceWorker.js and OidcTrustedDomains.js from the web root. If you need to install or update the OidcServiceWorker.js file into a static directory, you can run the following command:

node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs static

The OidcServiceWorker.js file should always be up to date with the version of the library. You can set up a dependencies script in your package.json file to update it at each npm install or update. For example:

1
2
3
4
5
{
  "scripts": {
    "dependencies": "[ -d './node_modules/@axa-fr/oidc-client' ] && node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs static || true"
  }
}

Just keep in mind that it doesn't work inside of a workspace if you are using a monorepo. You might need to use the right path to the root node_modules directory (like ../../node_modules) and/or run npm run dependencies manually every time the package @axa-fr/oidc-client is installed or updated. It might help to put this script in the root package.json of your monorepo.

Basic Usage

Minimal configuration to initialize the authentication module:

1
2
3
4
5
import { AuthOidc } from "@codecoupler-pro/app-oidc";
let auth = new AuthOidc({
  client_id: "interactive.public",
  authority: "https://demo.duendesoftware.com"
});

Handling Authentication via Popup

If you're using the authentication via popup to avoid a full page reload, make sure that if your application is loaded with URLs including the hashes #authentication-callback, #authentication-login, and #authentication-logout, you won't build any user interface.

In both cases, your application will be loaded inside the pop-up window. The module will then either redirect or close the pop-up window automatically, or it will write some info messages into the body (replacing everything inside).

You can use the instance method insidePopup to check this:

Generic example:

1
2
3
4
5
6
let auth = new AuthOidc({
  /* ... */
});
if (!auth.insidePopup) {
  // Build your UI here
}

Example using CodeCoupler UI:

import { AuthOidc } from "@codecoupler-pro/app-oidc";
import { Interceptors } from "@codecoupler/cc-ui";
let auth = new AuthOidc({
  /* ... */
});
if (auth.insidePopup) {
  Interceptors.register(
    "authentication-callback",
    () => new Promise(() => true)
  );
  Interceptors.register("authentication-login", () => new Promise(() => true));
  Interceptors.register("authentication-logout", () => new Promise(() => true));
}

The hash names can be modified in the custructor:

1
2
3
4
5
let auth = new AuthOidc({
  callback_hash: "authentication-callback",
  login_hash: "authentication-login",
  logout_hash: "authentication-logout"
});

React to Identity Changes

The best way to keep track of identity changes is to use the reactive property identity with:

import { watch } from "vue";

watch(
  () => authInstance.identity,
  () => {
    // Check here authInstance.identity.authenticated ||
    // Check here authInstance.roles.includes("some-roles");
  },
  { deep: true, immediate: true }
);

Reference

Constructor Options

You can use every option provided by the package @axa-fr/oidc-client.

These scopes will be used by default:

  • openid: Required to use OIDC Flow
  • offline_access: Required to get Refresh Tokens
  • profile: Get Names, Username, Adresses etc.
  • email: Get Email and if Email is Verfied

Add this scope to get the roles from Keycloak:

  • roles: Get Roles
1
2
3
4
5
let auth = new AuthOidc({
  client_id: "interactive.public",
  authority: "https://demo.duendesoftware.com"
  scope: "openid offline_access profile email roles",
});

Instance Members

Login/Logout Functions for Popup Authentication

async login(width = 800, height = 650, title = "Login")

Login via popup. Returns a promise that resolves to true if the login was successful. You can change the size and title of the pop-up using the arguments. If you call this function before the promise is resolved, it'll just show the existing pop-up.

async loginForce(width = 800, height = 650, title = "Login")

Login via popup. Try logging in even if the user is already logged in. The return value, the arguments, and the behavior are exactly like in login.

async logout(width = 800, height = 650, title = "Logout")

Logout via popup. Returns a promise that resolves to true if the logout was successful. You can change the size and title of the pop-up using the arguments. If you call this function before the promise is resolved, it'll just show the existing pop-up.

async logoutForce(width = 800, height = 650, title = "Logout")

Logout via popup. Try logging out, even if you're already logged out. The return value, the arguments, and the behavior are exactly like in logout.

async logoutLocal()

Just log out without having to reload the page or open a pop-up. This will only get rid of the local stored tokens. The session of the authentication server will not be ended. Returns a promise that resolves to true if the logout was successful.

async loginLocalForce()

Just log out without having to reload the page or open a pop-up. Try logging out, even if the user isn't logged in. The return value, the arguments, and the behavior are exactly like in logoutLocal.

Login/Logout Functions for Redirect Authentication

async loginReload()

Login via redirect. If the login can't be completed, the returned promise resolves to false.

async loginReloadForce()

Login via redirect. Try logging in even if the user is already logged in. Returns a promise that resolves to false if the login doesn't work.

async logoutReload()

Logout via redirect. If the logout can't be completed, the returned promise resolves to false.

async loginReloadForce()

Logout with redirect. Try to logout even if the user is logged out. Returns a promise that resolves to false if logout is not possible.

Identity Info Functions

async getIdentity()

This returns a promise that resolves with an identity object containing info about the current user. It'll wait for the OIDC library to initialize.

async isAuthenticated()

Returns true if authenticated. It'll wait for the OIDC library to initialize.

isAuthenticatedSync()

Returns true if authenticated. It'll give you the current status even if the library hasn't been initialized yet.

getIdentitySync()

Returns an object containing info about the current user. It'll give you the current status even if the library hasn't been initialized yet.

get identity

Reactive object like the return value of getIdentity. Whenever the identity changes the reactive object will change its values.

The identity is an object with the properties:

{
  authenticated,
  id,
  username,
  given_name,
  family_name,
  email,
  roles,
  groups,
  permissions,
  tokens
}

The property authenticated is a Boolean value. It tells you if the user is logged in or not.

The property roles is an empty array if not authenticated. The arrays groups and permissions are always empty. These arrays are only used to make it compatible with other access control methods.

All other properties have the value null if not authenticated and they're shortcuts to properties from token payloads.

The property tokens is an object that includes all tokens and their payloads like: accessToken (if not hidden by the service worker, see documentation of @axa-fr/oidc-client for more details) and idTokenPayload.

Helper

async removeWorker()

Try to unregister the service worker from all open tabs. Reload after completion the window: auth.removeWorker().then(() => window.location.reload());

get oidcClient

Origin OIDC library instance.