Securing Nuxt Apps with Keycloak
Tags: nuxt, keycloak, authentication, tutorial
Published: 2023-09-11 11:26:22

---
canonical_url: https://phasetwo.io/blog/instant-user-managemenet-and-sso-for-nuxt
---

[Nuxt](https://nuxt.com/) is an open source web framework that makes developing web applications easier.

In this article we'll be using [Keycloak](https://www.keycloak.org/) to secure a Nuxt Web application. We will use two different methods: [`keycloak-js`](https://www.npmjs.com/package/keycloak-js) and [`oidc-client-ts`](https://www.npmjs.com/package/oidc-client-ts).

{% cta https://phasetwo.io/ %} Phase Two {% endcta %} is a [Keycloak as a Service](https://phasetwo.io/) provider enabling SaaS builders to accelerate time-to-market with powerful enterprise features like SSO, identity, and user management features. Phase Two enhances Keycloak through a variety of open-source extentions for modern SaaS use cases. Phase Two supports both hosted and on-premise deployment options. 

_What is Keycloak?_

[Keycloak](https://www.keycloak.org/) has been a leader in the Identity and Access Management world since its launch almost 8 years ago. It is an open-source offering under the stewardship of [Red Hat](https://www.redhat.com/)

> **INFO**
If you just want to skip to the code, visit the Phase Two [Nuxt example](https://github.com/p2-inc/examples/tree/main/frameworks/nuxt). We are also building [Keycloak examples](https://github.com/p2-inc/examples) for other frameworks.


## TOC

* [Keycloak Setup](#keycloak-setup)
* [Nuxt + Keycloak-Js](#nuxt-kjs)
* [Nuxt + OIDC Client](#nuxt-oidc)

---

## Setting up a Keycloak Instance <a name="keycloak-setup"></a>

> **TIP**
If you already have a functioning Keycloak instance, you can skip to the next section.

{% details **Keycloak Setup Details** %} 
Rather than trying to set up a "from scratch" instance of Keycloak, we're going to short-circuit that process by leveraging a Phase Two [free Keycloak](https://phasetwo.io/) starter instance. The Starter provides a free hosted instance of Phase Two's enhanced Keycloak ready for light production use cases.

- Visit the sign-up [page](https://phasetwo.io/dashboard/).
- Enter an email, use a Github account, or use an existing Google account to register.

![Register](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1jovhdjmmnys3kjp1wb.png)

- Follow the register steps. This will include a sign-in link being sent to your email. Use that for password-less login.

![Email Link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s73nhw4jl43li164nm5i.png)

- After creating an account, a [realm](https://www.youtube.com/watch?v=ZTFlc-3pG1M) is automatically created for you with all of the Phase Two enhancements. You need to create a Deployment in the Shared Phase Two infrastructure in order to gain access to the realm. Without a deployment created, the Create Shared Deployment modal will automatically pop up.
- Create a Shared Deployment by providing a region (pick something close to your existing infrastructure), a name for the deployment, and selecting the default organization that was created for you upon account creation. Hit "Confirm" when ready. Standby while our robots get to work generating your deployment. This can take a few seconds.

  ![Create shared deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7oknshhzra67byi0uxoz.png)

- After the deployment is created and active, you can access the Keycloak Admin console by clicking "Open Console" for that deployment. Open it now to see the console.

![Deployments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arwn5n54hbzwocobkmzs.png)

At this point, move on to the next step in the tutorial. We'll be coming back to the Admin Console when its time to start connecting our App to the Keycloak instance.
{% enddetails %}


## Setting up an OIDC Client

We need to create a [OpenID Connect](https://www.keycloak.org/docs/latest/server_admin/#con-oidc_server_administration_guide) Client in Keycloak for the app to communicate with. 

{% details **Details** %}

Keycloak's [docs](https://www.keycloak.org/docs/latest/server_admin/#_oidc_clients) provide steps for how to create an OIDC client and all the various configurations that can be introduced. Follow the steps below to create a client and get the right information necessary for app configuration.

1.  Open the Admin UI by clicking **Open Console** in the Phase Two Dashboard.
1.  Click **Clients** in the menu.
1.  Click **Create client**.
1.  Leave **Client type** set to **OpenID Connect**.
1.  Enter a **Client ID**. This ID is an alphanumeric string that is used in OIDC requests and in the Keycloak database to identify the client.
1.  Supply a **Name** for the client.
1.  Click **Next**.
![General settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vmlbu0gbf3wdmmacy23.png)
1.  Under the Capability Config section, leave the defaults as selected. This can be configured further later.
- Client authentication to Off.
- Authorization to Off.
- Standard flow checked. Direct access grants checked. All other items unchecked.
1.  Click **Next**.
![Capbility config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rv9fpa1jgny2300c6btd.png)
1.  Under Login settings we need to add a redirect URI and Web origin in order. Assuming you are using the example application:
**URI and Origin Details**
The choice of `localhost` is arbitrary. If you are using an example application running locally, this will apply. If you are using an app that you actually have deployed somewhere, then you will need to substitute the appropriate URI for that.

    _Valid redirect URI_ (allows redirect back to application)
    ```
    http://localhost:3000/*
    ```
    _Web origins_ (allows for Token auth call)
    ```
    http://localhost:3000
    ```

1.  Click **Save**
![Login settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lk6j7sjbv2b5ns7jc85a.png)
{% enddetails %}

### OIDC Config

{% details **Details** %}

We will need values to configure our application. To get these values follow the instructions below.

1. Click **Clients** in the menu.
1. Find the Client you just created and click on it. In the top right click the **Action** dropdown and select **Download adapter config**.
1. Select **Keycloak OIDC JSON** in the format option. The details section will populate with the details we will need.
   - Note the `realm`, `auth-server-url`, and `resource` values.
![Adapter config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjxmpf25hhyogdno2yht.png)

{% enddetails %}

## Adding a Non-Admin User

> **INFO**
It is bad practice to use your Admin user to sign in to an Application.

Since we do not want to use our Admin user for signing into the app we will build, we need to add another non-admin user.

{% details **Details** %}
1.  Open the Admin UI by clicking **Open Console** in the Phase Two Dashboard.
1.  Click **Users** in the menu.
1.  Click **Add user**.
1.  Fill out the information for Email, First name, and Last name. Click **Create**.
1.  We will now set the password for this user manually. Click **Credentials** (tab) and click **Set Password**. Provide a password for this user. For our use case, as a tutorial, you can leave "Temporary" set to "Off".
1.  Click **Save** and confirm the password by clicking **Save password**
{% enddetails %}

---

## Setting up a Nuxt Project

> **INFO**
We will use the Phase Two Nuxt example code here, but the logic could easily be applied to any existing application.

This example uses Nuxt3. There are a couple methods by which you can integrate Keycloak to your Nuxt application. We're going to explore two methods here, one uses [`keycloak-js`](https://www.npmjs.com/package/keycloak-js) and the other leverages [`oidc-client-ts`](https://www.npmjs.com/package/oidc-client-ts). The `keycloak-js` library provides a simple, client-only method, but lacks some of the sophistication provided by the `oidc-client` library that is heavily supported and more widely used.
 
### Using `keycloak-js` <a name="nuxt-kjs"></a>

> **INFO**
For this example, we need to disable "Client Authentication" in the OIDC client that was setup earlier. This is available under Client > Settings > Capability config > Client authentication to OFF.

1. Clone the Phase Two [example repo](https://github.com/p2-inc/examples/).
1. Open the Nuxt [folder](https://github.com/p2-inc/examples/tree/main/frameworks/nuxt) within `/frameworks/nuxt` and open the `keycloak-js` folder within `/frameworks/nuxt/keycloak-js`.
1. Run `npm install` and then `npm run dev`. [`keycloak-js`](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter) is a Javascript library that provides a fast way to secure an application.
1. The project makes use of the following Nuxt items: components, composables, layouts, and plugins. We'll review each in kind.
1. The main component that shows the User's authenticated state is in `/components/User`. In this component we call the `useKeycloak` composable, which let's us key into the `keycloak-js` functions that we've wrapped to make easily availble.

   ```javascript
   const { keycloak, authState } = useKeycloak();

   function login() {
     keycloak.login();
   }

   function logout() {
     keycloak.logout();
   }
   ```

   Lower in the file the component leverages `v-if` checks to determine if the `authState` is `authenticated` or not. Depending on the state, a Log in or Log out button is available.

1. Let's take a look at the setup for the composable next. Our composable is in `/composables/keycloak-c`. A composable is a function defined that can be called anywhere in the Nuxt application. It's a good way to abstract logic to be reused. In our case we use it to wrap a `keycloak-js` plugin (more on that in the next step) and help provided a state value for the authenticated state.

   ```javascript
   export const useKeycloak = () => {
     const nuxtApp = useNuxtApp();
     const keycloak = nuxtApp.$keycloak as Keycloak;
     const authState = useState("authState", () => "unAuthenticated");

     keycloak.onAuthSuccess = () => (authState.value = "authenticated");
     keycloak.onAuthError = () => (authState.value = "error");

     return {
       keycloak,
       authState,
     };
   };
   ```

1. In the plugin, `/plugins/keycloak.client.ts` we instantiate the `keycloak-js` library. We can then attach that instance to the `NuxtApp` instance. Substitute the correct values for your Keycloak instance that we created earlier in the tutorial.

   ```javascript
   export default defineNuxtPlugin((nuxtApp) => {
     const initOptions: KeycloakConfig = {
       url: "https://euc1.auth.ac/auth/",
       realm: "shared-deployment-001",
       clientId: "reg-example-1",
     };

     const keycloak = new Keycloak(initOptions);

     nuxtApp.$keycloak = keycloak;

     keycloak.init({
       onLoad: "check-sso",
     });
   });
   ```

1. The logic for checking the `authenticated` state can be used to expand in ways to secure your site in a number of ways.

### Using `oidc-client` <a name="nuxt-oidc"></a>

The [`oidc-client-ts`](https://www.npmjs.com/package/oidc-client-ts) package is a well-maintained and used library. It provides a lot of utilities for building out a fully production app.

1. Clone the Phase Two [example repo](https://github.com/p2-inc/examples/).
1. Open the Nuxt [folder](https://github.com/p2-inc/examples/tree/main/frameworks/nuxt) within `/frameworks/nuxt` and open the `/nuxt/oidc-client-ts` folder.
1. Run `npm install` and then `npm run dev`.
1. The structure of the project is similar to the `keycloak-js` version but with a the use of services, stores, and middleware.
1. We'll review where we configure out Keycloak instance. First open `/services/keycloak-config.ts`. In this file you will want to update it with the values for the Keycloak instance we set-up earlier in the tutorial. Make sure you are using the one with Client Authentication enabled. Update the `clientSecret` with the value. Use and environment variable here if you wish.

   ```json
   export const keycloakConfig = {
     authorityUrl: "https://euc1.auth.ac",
     applicationUrl: "http://localhost:3000",
     realm: "shared-deployment-001",
     clientId: "reg-example-1",
     clientSecret: "CLIENT_SECRET",
   };
   ```

1. Switch over to the `/services/auth-service` now to see how the Oidc instance is started. The class pulls in values from the `keycloakConfig` to use in the constructor. The other functions are wrappers around methods provided by the `oidc-client` library. This allows us to key into things like `signInRedirect` and `signoutRedirect`.

   How the settings are integrated:

   ```javascript
   const settings = {
     authority: `${keycloakConfig.authorityUrl}/auth/realms/${keycloakConfig.realm}`,
     client_id: keycloakConfig.clientId,
     client_secret: keycloakConfig.clientSecret,
     redirect_uri: `${window.location.origin}/auth`,
     silent_redirect_uri: `${window.location.origin}/silent-refresh`,
     post_logout_redirect_uri: `${window.location.origin}`,
     response_type: "code",
     userStore: new WebStorageStateStore(),
     loadUserInfo: true,
   };
   this.userManager = new UserManager(settings);
   ```

   Example function wrapper:

   ```javascript
   public signInRedirect() {
     return this.userManager.signinRedirect();
   }
   ```

1. With the `AuthService` defined, we can now expose that through a composable. Switch to the `/composables/useServices` file. The file is simple but provides a way for any component to hook into the service instance.

   ```javascript
   import AuthService from "@/services/auth-service";
   import ApplicationService from "@/services/application-service";
   import { useAuth } from "@/stores/auth";

   export const useServices = () => {
     const authStore = useAuth();

     return {
       $auth: new AuthService(),
       $application: new ApplicationService(authStore.access_token),
     };
   };
   ```

   We pull in the `AuthService` then expose it through the `$auth` variable. The `$application` variable exposes the `ApplicationService` which is provided as an example of how you could secure API calls.

1. We leverage the [`pinia`](https://pinia.vuejs.org/) library to make store User information to make it easily accessible. Open `/stores/auth/index`. From within this file, we can wrap the `User` object exposed by the `oidc-client` package. This can then be leveraged in the middleware function we want to define or to pull information quickly about the user.
1. There are a few main pages in play here that we define to create paths the library can leverage. The `/pages/auth`, `/pages/logout`, `/pages/silent-refresh` create paths at the same name. These are used to do the redirection during authentication or log out. From within these we use the `AuthService` to direct the user around within the app. For instance in `/auth`:

   ```javascript
   const authenticateOidc = async () => {
     try {
       await services.$auth.signInCallback();
       router.push("/");
     } catch (error) {
       console.error(error);
     }
   };

   await authenticateOidc();
   ```

   The `router.push` naively sends someone to the home page. This could be updated to go to any number of places, including the page one started the login flow from if you were to store that information to be retrieved.

1. We have also created a middleware file in `/middleware/auth.global` to be used in a couple of ways. It checks if the user is authenticated and based on that knowledge, stores the user information in the store (if not there) or could be used to send someone to login. For our example, we created buttons to initiate that but there is a comment which shows how you could force a set of paths to require login.

   ```javascript
   const authFlowRoutes = ["/auth", "/silent-refresh", "/logout"];

   export default defineNuxtRouteMiddleware(async (to, from) => {
     const authStore = useAuth();
     const services = useServices();
     const user = (await services.$auth.getUser()) as User;

     if (!user && !authFlowRoutes.includes(to.path)) {
       // use this to automatically force a sign in and redirect
       // services.$auth.signInRedirect();
     } else {
       authStore.setUpUserCredentials(user);
     }
   });
   ```

1. Now that we have all the things setup, we can define the user component `/components/User` to easily pull information about the user's state and display the appropriate UI.

   ```javascript
   const authStore = useAuth();
   const user = authStore.user;

   const signIn = () => services.$auth.signInRedirect();
   const signOut = () => services.$auth.logout();
   ```

   With this, the user object is now easily available. A simple `v-if="user"` allows the app to determine what UI to show.

1. A bit more complicated of a setup, but more elegant in the handling of the logged in flow. The `oidc-client` allows for much better fine-tuning of the experience.

## Learning more

Phase Two's enhanced Keycloak provides many ways to quickly control and tweak the log in and user management experience. Our [blog](https://phasetwo.io/blog) has many use cases from [customizing login pages](https://phasetwo.io/blog/customizing-login-pages), setting up [magic links](https://phasetwo.io/blog/set-up-magic-links) (password-less sign in), and [Organization](https://phasetwo.io/product/organizations) workflows.