Single Sign-On (SSO) with Zoho in Vue3
Tags: oidc, vue, vueoidc, openidentityauthentication
Published: 2023-08-13 00:00:00

---
title: Single Sign-On (SSO) with Zoho in Vue3
published: true
description: It seeks to enlighten you on how to go about integrating the oidc (using zoho as the authority/provider) in your vue 3 application.
tags: oidc, vue, vueoidc, openidentityauthentication
cover_image: https://res.cloudinary.com/odumz/image/upload/v1691857787/Articles/VueOIDC/oidc-with-zoho-vue_oz3vx6.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-08-19 00:00 +0000
---

Hi fellas,

So I was recently working on a project where I had to use [OIDC](https://auth0.com/docs/authenticate/protocols/openid-connect-protocol) 🔐for authentication on the app. Apparently, it was a tad bit more difficult than I expected, mostly due to lack of proper documentation especially for the [Vue 3](https://vuejs.org/guide/introduction.html) framework.

You know how you feel like a boss 😎 when you're finally able to figure things out and make it work? Yup. That was soo me. My head was through the roof but I felt like I went through so much. 

So if you ever, find yourself where I started, with little to no proper documentation, I hope this helps you feel like a boss in less time and with less stress.
<br />

## A little introduction
Authentication and security are essential aspects of web development, and OIDC (OpenID Connect) has emerged as a popular authentication protocol due to its simplicity and robustness. 

In this article, we will explore how to integrate OIDC into a Vue.js (version 3) application using [Zoho](https://www.zoho.com/) as the identity provider. Zoho, a renowned cloud-based software suite with amazing products, offers a secure and reliable OIDC-based authentication service that enables developers to implement seamless user authentication in their Vue.js applications.
<br />

## What is OIDC?
OIDC which stands for Open-Identity Connect, is an extension of OAuth 2.0, designed to provide a standardized way for users to log in and authenticate with web applications. It allows for the secure transmission of user identity information between the identity provider (IdP) and the client application, ensuring a seamless and consistent user experience across different platforms.

![Single Sign-On Authentication](https://res.cloudinary.com/odumz/image/upload/v1691857836/Articles/VueOIDC/sso-auth_eczjv3.png)

Simply put, it is an open standard that allows users to authenticate to applications using their existing identity providers. This means that users can sign in to your application using their Zoho credentials, without having to create a new account.
<br />

## What is Vue?
Should you even be reading this if you don't know what Vue is?😅 Hey, don't get me wrong, you just need to learn about a tool before you start using it so you can maximize your effort. You can check [the Vue article](https://vuejs.org/guide/introduction.html) to learn about Vue and then come back to implement this. I'll definitely be rooting for you.

### How to use OIDC in Vue
Before integrating OIDC into a Vue.js application, you need to set up Zoho as the identity provider:

* Create a Zoho Developer Account: Visit the [Zoho Developer Console](https://accounts.zoho.com/developerconsole) and sign in or sign up for an account.

* Register Your Application: In the Zoho Developer Console, create a new "Client" to register your application. You will receive a Client ID and Client Secret that you'll use later in the Vue.js application.

* Define Redirect URLs: Specify the allowed redirect URLs where Zoho can send authentication responses after successful login. This should eventually resolve to this -
`redirect_uri=https://your.url.com/signin-oidc&response_type=code&prompt=consent&scope=openid profile AaaServer.profile.Read email&code_challenge=xxxx-xxx-xxxx-xxxxxx-xxx&code_challenge_method=S256&response_mode=form_post&state=xxxx-xxx-xxxx-xxxxxx-xxx&x-client-SKU=XX_XXX-1.0&x-client-ver=6.21.0.0`

<br />

## Let's get to coding!

To begin, we would need to set up an application using vue and oidc. We are going to install a library known as vue-oidc-client. This is a vue wrapper around oidc-client-js to help it work well with Vue. 

### Integrating OIDC into Vue.js
Now, let's dive into integrating OIDC into your Vue.js application:

* Setting Up Vue.js Project: Start by creating a new Vue.js project using Vue CLI or any other preferred method. [See here](https://vuejs.org/guide/quick-start.html) on how to.

* Install Required Dependencies: To handle OIDC authentication, you'll need some packages like oidc-client and vue-oidc-client. Install them using npm or yarn:

```javascript
npm install oidc-client vue-oidc-client

yarn add oidc-client vue-oidc-client
```

* Next, create a `.env` file for your credentials or secret

```vue
VITE_APP_CLIENT_ID=whatever_your_client_id_is
VITE_APP_BASE_URL=whatever_your_base_url_is
VITE_APP_OIDC_AUTHORITY=whatever_your_zoho_oidc_endpoint_is
```

* Create a config file to hold the settings `config.ts`

```typescript
export const client_id:string = import meta.env.VITE_APP_CLIENT_ID;
export const base_url:string = import meta.env.VITE_APP_BASE_URL;
export const oidc_authority:string = import meta.env.VITE_APP_OIDC_AUTHORITY;
```

* Implement the oidc service `idsrvAuth.ts`

```typescript
import { User } from 'oidc-client'
import { createOidcAuth, SignInType, LogLevel } from 'vue-oidc-client/vue3'
import { client_id, base_url, oidc_authority } from './config'

const appRootUrl:string = `${base_url}/`

// OR
// const loco = window.location
// const appRootUrl = `${loco.protocol}${loco.host}/`
// const appRootUrl = 'localhost:8000/'
const provider:string = 'Zoho'

const authCallbackPath:string = 'index.html?auth-callback=1';
const logoutCallbackPath:string = '/index.html?logout-callback=1';
const prompt:string = 'login'
const redirectUri:string = `${base_url}/${authCallbackPath}`; // remove / for local
const scopes:string = 'offline_access';
const logoutRedirectUri:string = `${oidc_authority}${logoutCallbackPath}`;

const idsrvAuth = createOidcAuth(
  'main',
  SignInType.Popup, // SignInType.Window on local
  appRootUrl,
  {
    authority: oidc_authority,
    client_id: client_id,
    redirect_uri: redirectUri,
    post_logout_redirect_uri: 'https://your-client-url/',
    response_type: 'code',
    filterProtocolClaims: true,
    loadUserInfo: true,
    scope: scope,
    extraQueryParams: { prompt: prompt, provider: provider }
  },
  console,
  LogLevel.Debug
)

// handle events
idsrvAuth.events.addAccessTokenExpiring(function() {
  // eslint-disable-next-line no-console
  console.log('access token expiring')
})

idsrvAuth.events.addAccessTokenExpired(function() {
  // eslint-disable-next-line no-console
  console.log('access token expired')
})

idsrvAuth.events.addSilentRenewError(function(err: Error) {
  // eslint-disable-next-line no-console
  console.error('silent renew error', err)
})

idsrvAuth.events.addUserLoaded(function(user: User) {
  // eslint-disable-next-line no-console
  console.log('user loaded', user)
// save token to storage for use later
  localStorage.setItem('token', user?.access_token)
})

idsrvAuth.events.addUserUnloaded(function() {
  // eslint-disable-next-line no-console
  console.log('user unloaded')
})

idsrvAuth.events.addUserSignedOut(function() {
  // eslint-disable-next-line no-console
  console.log('user signed out')
})

idsrvAuth.events.addUserSessionChanged(function() {
  // eslint-disable-next-line no-console
  console.log('user session changed')
})

export default idsrvAuth
``` 

* Edit your `main.ts` file
```typescript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import idSrvAuth from './idSrvAuth'
...

try {
    const start:any = idSrvAuth.startup();
    if (start) {
        const app:any = createApp(App).use(router);

        app.config.globalProperties.$oidc = idSrvAuth

        app.config.globalProperties.filtersLimit = (value:any, size:any) => {
            if (!value) return '';
            value = value.toString();

            if (value.length <= size) {
            return value;
            }
            return value.substr(0, size) + '...';
        }

        app.mount('#app')
    }
}
catch(err:any) {
    console.log(`Startup was not okay: ${err}`)
}
```

* Next up, update your router with the `idsrvAuth` service
```typescript
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import idsrvAuth from '../idSrvAuth';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Login',
    component: () => import('../views/auth/Login.vue'),
    meta: { requiresAuth: false },
  },
  {
    path: '/dashboard',
    name: 'Layout',
    component: () => import('../views/dashboard/Layout.vue'),
    meta: { authName: idsrvAuth.authName },
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: () => import('../views/dashboard/Index.vue'),
        meta: {
          requiresAuth: true,
        },
      },
      {
        path: 'profile',
        name: 'Profile',
        component: () => import('../views/dashboard/Profile.vue'),
        meta: { requiresAuth: true },
      },
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'Error',
    component: () => import('../components/NotFound.vue'),
    meta: { requiresAuth: false },
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach(async (to, from) => {
  const token:any = localStorage.getItem('token')
  if (to.meta.requiresAuth) {
    if (!token) {
      router.push({ name: 'Login' })
    }
  }
})

idsrvAuth.useRouter(router);

export default router;
```

* Finally, you can use in your form like this `loginForm.vue`
```vue
<script lang="ts">
export default {
  name: 'LoginForm',
}
</script>

<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import SvgIcons from '../SvgIcons.vue';
import spinner from '../spinner.vue'

const route = useRouter();

const loginWithZoho:any = async () => {
    route.push('/dashboard')
}
</script>

<template>
    <div class="relative main w-full bg-white">
        <form class="text-sm grid">
            <div class="grid gap-1 mb-4">
                <label for="username" class="font-semibold">
                    Username
                </label>
                <input type="text" name="username" v-model="data.username" id="username" @blur="checkError" placeholder="Enter username" :class="[errors.username ? 'border text-red border-red' : '']" class="p-4 rounded-md bg-gray-50 text-xs focus:outline-none">
                <p class="text-[10px] text-red">
                    {{ errors.username ? errors.usertext : '' }}
                </p>
            </div>
            <div class="grid gap-1 mt-4 mb-2">
                <label for="password" class="font-semibold">
                    Password
                </label>
                <input type="password" @blur="checkError" name="password" id="password" placeholder="Enter password" v-model="data.password" :class="[errors.password ? 'border text-red border-red' : '']" class="p-4 rounded-md bg-gray-50 text-xs focus:outline-none">
                <p class="text-[10px] text-red">
                    {{ errors.password ? errors.passwordtext : '' }}
                </p>
            </div>
            <div class="extras flex justify-between items-center text-[10px] text-blue-600 mt-2 mb-8">
                <div class="forgot">
                    <button @click.prevent="forgotPassword">
                        Forgot password?
                    </button>
                </div>
                <div class="remember flex">
                    <div class="flex items-center">
                        <input id="list-check" @click.prevent="check" type="checkbox" class="opacity-0 absolute h-5 w-5" />
                        <div class="bg-primary text-primary border-2 w-5 h-5 flex flex-shrink-0 justify-center items-center mr-2 focus-within:border-primary">
                        <SvgIcons v-if="rememberChecked" class="text-white" name="tick" />
                        </div>
                    </div>
                    <label for="remember">
                        Remember me
                    </label>
                </div>
            </div>
            <div class="grid">
                <button @click.prevent="submit" :disabled="isDisabled" :class="[isDisabled ? 'bg-grey' : 'bg-primary']" class="p-4 font-bold flex justify-center border text-white rounded-md">
                    <span class="px-4" :class="[isLoading ? '' : 'hidden']">
                        <spinner />
                    </span>
                    Login
                </button>
            </div>
        </form>
        <div class="flex items-center justify-center mt-8 gap-3">
            <hr class="bg-primary w-full h-[2px]"/>
            <p>OR</p>
            <hr class="bg-primary w-full h-[2px]"/>
        </div>
        <div class="my-5">
            <button @click="loginWithZoho" class="bg-[#CE2232] flex w-full font-semibold bg-opacity-70 hover:bg-opacity-100 justify-center text-white py-3 px-5 rounded">
                Sign in with Zoho
            </button>
        </div>
    </div>
</template>

<style scoped>
</style>
```
When the user clicks the button, the `loginWithZoho()` method will redirect the user to the Zoho OAuth 2.0 authorization endpoint. If the user authenticates successfully, they will be redirected back to your application, which will then redirect them to the `/dashboard route`.
<br />

## Conclusion
In this article, you saw how to use OIDC with the router in Vue to create a secure and user-friendly authentication flow for your application. We used Zoho as our identity provider, but the same principles can be applied to any OIDC-compliant provider.

By integrating OIDC into your Vue.js application using Zoho as the identity provider, you can achieve a secure and seamless authentication experience for your users. OIDC's standardized approach and Zoho's reliable authentication service provide a robust foundation for any web application, ensuring user data remains protected while enabling easy access across multiple platforms.

Remember to explore further customization options, such as handling user roles, accessing user information, and handling token expiration, to tailor the OIDC integration to your application's specific requirements. Happy coding!
<br />

## Resources

You can find the wiki docs here - 
* [Vue OIDC Client - Docs](https://github.com/soukoku/vue-oidc-client/wiki/V1-Docs)

* [Sample Application](https://github.com/soukoku/vue-oidc-client/tree/master/vue3/src/sample-app)
<br />

## Reach me
* [Twitter](https://twitter.com/0dumz)
* [LinkedIn](https://www.linkedin.com/in/odumz)