Skip to content

Instantly share code, notes, and snippets.

@K3TH3R
Created October 3, 2020 01:47
Show Gist options
  • Save K3TH3R/416e5c6627436fa87db16f57f50496d1 to your computer and use it in GitHub Desktop.
Save K3TH3R/416e5c6627436fa87db16f57f50496d1 to your computer and use it in GitHub Desktop.
import createAuth0Client from '@auth0/auth0-spa-js'
import { computed, reactive, watchEffect } from 'vue'
let client
const state = reactive({
loading: true,
isAuthenticated: false,
user: {},
popupOpen: false,
error: null,
})
/**
* Authenticates the user using a popup window
*
* @param {Object} o
*/
async function loginWithPopup() {
state.popupOpen = true
try {
await client.loginWithPopup(0)
} catch (e) {
console.error(e)
} finally {
state.popupOpen = false
}
state.user = await client.getUser()
state.isAuthenticated = true
}
/**
* Handles the callback when logging in using a redirect
*
* @param {Object} o
*/
async function handleRedirectCallback() {
state.loading = true
try {
await client.handleRedirectCallback()
state.user = await client.getUser()
state.isAuthenticated = true
} catch (e) {
state.error = e
} finally {
state.loading = false
}
}
/**
* Authenticates the user using the redirect method
*
* @param {Object} o
*/
function loginWithRedirect(o) {
return client.loginWithRedirect(o)
}
/**
* Returns all the claims present in the ID token
*
* @param {Object} o
*/
function getIdTokenClaims(o) {
return client.getIdTokenClaims(o)
}
/**
* Returns the access token. If the token is invalid or missing,
* a new one is retrieved
*
* @param {Object} o
*/
function getTokenSilently(o) {
return client.getTokenSilently(o)
}
/**
* Gets the access token using a popup window
*
* @param {Object} o
*/
function getTokenWithPopup(o) {
return client.getTokenWithPopup(o)
}
/**
* Logs the user out and removes their session on the authorization server
*
* @param {Object} o
*/
function logout(o) {
return client.logout(o)
}
const authPlugin = {
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
loginWithPopup,
logout,
}
/**
* Authorization guard to protect routes in our app from unauthorized users
*
* @param {*} to
* @param {*} from
* @param {*} next
*/
const routeGuard = (to, from, next) => {
const { isAuthenticated, loading, loginWithRedirect } = authPlugin
const verify = () => {
// If the user is authenticated, continue with the route
if (isAuthenticated.value) {
return next()
}
// Otherwise, log in
loginWithRedirect({ appState: { targetUrl: to.fullPath } })
}
// If loading has already finished, check our auth state using `fn()`
if (!loading.value) {
return verify()
}
// Watch for the loading property to change before we check isAuthenticated
watchEffect(() => {
if (loading.value === false) {
return verify()
}
})
}
async function init(options) {
const { onRedirectCallback, redirectUri = window.location.origin } = options
client = await createAuth0Client({
domain: process.env.VUE_APP_AUTH0_DOMAIN,
client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
audience: options.audience,
redirect_uri: redirectUri,
})
try {
// If the user is returning to the app after authentication
if (
window.location.search.includes('code=') &&
window.location.search.includes('state=')
) {
// handle the redirect and retrieve tokens
const { appState } = await client.handleRedirectCallback()
// Notify subscribers that the redirect callback has happened, passing the appState
// (useful for retrieving any pre-authentication state)
onRedirectCallback(appState)
}
} catch (e) {
state.error = e
} finally {
// Initialize our internal authentication state
state.isAuthenticated = await client.isAuthenticated()
state.user = await client.getUser()
state.loading = false
}
return {
install: (app) => {
app.provide('Auth', authPlugin)
},
}
}
export default {
init,
routeGuard,
}
import { createApp } from 'vue'
import App from './App.vue'
import Auth from './plugins/auth0'
import router from './router'
async function init() {
const AuthPlugin = await Auth.init({
onRedirectCallback: (appState) => {
router.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname,
)
},
})
createApp(App).use(AuthPlugin).use(router).mount('#app')
}
init()
import { createRouter, createWebHistory } from 'vue-router'
import Auth from '../plugins/auth0'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/profile',
name: 'Profile',
beforeEnter: Auth.routeGuard,
component: () =>
import(/* webpackChunkName: "profile" */ '../views/Profile.vue'),
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
@ocruzv
Copy link

ocruzv commented Oct 16, 2021

Here's a sightly modified TS version that reads a meta.requiresAuth flag in the router instead of defining beforeEnter on each route if we pass a general beforeEnter: Auth.routeGuard to the createRouter options:

import createAuth0Client, {
  Auth0Client,
  Auth0ClientOptions,
  GetIdTokenClaimsOptions,
  GetTokenSilentlyOptions,
  GetTokenWithPopupOptions,
  LogoutOptions,
  PopupLoginOptions,
  RedirectLoginOptions,
  User,
} from '@auth0/auth0-spa-js';
import { App, computed, reactive, watchEffect, ComputedRef } from 'vue';
import { RouteLocationNormalized } from 'vue-router';

export type AuthState = {
  loading: boolean;
  isAuthenticated: boolean;
  user: User | undefined;
  popupOpen: boolean;
  error: any;
};

let client: Auth0Client;
const state: AuthState = reactive({
  loading: true,
  isAuthenticated: false,
  user: {},
  popupOpen: false,
  error: null,
});

/**
 * Authenticates the user using a popup window
 *
 * @param {PopupLoginOptions} o
 */
async function loginWithPopup(o?: PopupLoginOptions) {
  state.popupOpen = true;

  try {
    await client.loginWithPopup(o);
  } catch (e) {
    console.error(e);
  } finally {
    state.popupOpen = false;
  }

  state.user = await client.getUser();
  state.isAuthenticated = true;
}

/**
 * Handles the callback when logging in using a redirect
 *
 *  @param {string} url
 */
async function handleRedirectCallback(url?: string) {
  state.loading = true;

  try {
    await client.handleRedirectCallback(url);
    state.user = await client.getUser();
    state.isAuthenticated = true;
  } catch (e) {
    state.error = e;
  } finally {
    state.loading = false;
  }
}

/**
 * Authenticates the user using the redirect method
 *
 * @param {RedirectLoginOptions} o
 */
function loginWithRedirect(o?: RedirectLoginOptions) {
  return client.loginWithRedirect(o);
}

/**
 * Returns all the claims present in the ID token
 *
 * @param {GetIdTokenClaimsOptions} o
 */
function getIdTokenClaims(o?: GetIdTokenClaimsOptions) {
  return client.getIdTokenClaims(o);
}

/**
 * Returns the access token. If the token is invalid or missing,
 * a new one is retrieved
 *
 * @param {GetTokenSilentlyOptions & { detailedResponse: true; }} o
 */
function getTokenSilently(
  o?: GetTokenSilentlyOptions & {
    detailedResponse: true;
  }
) {
  return client.getTokenSilently(o);
}

/**
 * Gets the access token using a popup window
 *
 * @param {GetTokenWithPopupOptions} o
 */
function getTokenWithPopup(o?: GetTokenWithPopupOptions) {
  return client.getTokenWithPopup(o);
}

/**
 * Logs the user out and removes their session on the authorization server
 *
 * @param {LogoutOptions} o
 */
function logout(o?: LogoutOptions) {
  return client.logout(o);
}

export type AuthPlugin = {
  isAuthenticated: ComputedRef<boolean>;
  loading: ComputedRef<boolean>;
  user: ComputedRef<User | undefined>;
  getIdTokenClaims: typeof getIdTokenClaims;
  getTokenSilently: typeof getTokenSilently;
  getTokenWithPopup: typeof getTokenWithPopup;
  handleRedirectCallback: typeof handleRedirectCallback;
  loginWithRedirect: typeof loginWithRedirect;
  loginWithPopup: typeof loginWithPopup;
  logout: typeof logout;
};

const authPlugin: AuthPlugin = {
  isAuthenticated: computed(() => state.isAuthenticated),
  loading: computed(() => state.loading),
  user: computed(() => state.user),
  getIdTokenClaims,
  getTokenSilently,
  getTokenWithPopup,
  handleRedirectCallback,
  loginWithRedirect,
  loginWithPopup,
  logout,
};

/**
 * Authorization guard to protect routes in our app from unauthorized users
 *
 * @param {RouteLocationNormalized} to
 * @param {RouteLocationNormalized} from
 * @param {*} next
 */
const routeGuard = (
  to: RouteLocationNormalized,
  from: RouteLocationNormalized,
  next: Function
) => {
  const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
  if (!requiresAuth) return next();

  const { isAuthenticated, loading, loginWithRedirect } = authPlugin;

  const verify = () => {
    // If the user is authenticated, continue with the route
    if (isAuthenticated.value) {
      return next();
    }

    // Otherwise, log in
    loginWithRedirect({ appState: { targetUrl: to.fullPath } });
  };

  // If loading has already finished, check our auth state using `fn()`
  if (!loading.value) {
    return verify();
  }

  // Watch for the loading property to change before we check isAuthenticated
  watchEffect(() => {
    if (loading.value === false) {
      return verify();
    }
  });
};

async function init(options: Partial<Auth0ClientOptions>) {
  const { onRedirectCallback, redirectUri = window.location.origin } = options;

  client = await createAuth0Client({
    domain: import.meta.env.VITE_AUTH0_DOMAIN as string,
    client_id: import.meta.env.VITE_AUTH0_CLIENT_KEY as string,
    audience: options.audience,
    redirect_uri: redirectUri,
  });

  try {
    // If the user is returning to the app after authentication
    if (
      window.location.search.includes('code=') &&
      window.location.search.includes('state=')
    ) {
      // handle the redirect and retrieve tokens
      const { appState } = await client.handleRedirectCallback();

      // Notify subscribers that the redirect callback has happened, passing the appState
      // (useful for retrieving any pre-authentication state)
      onRedirectCallback(appState);
    }
  } catch (e) {
    state.error = e;
  } finally {
    // Initialize our internal authentication state
    state.isAuthenticated = await client.isAuthenticated();
    state.user = await client.getUser();
    state.loading = false;
  }

  return {
    install: (app: App<Element>) => {
      app.provide('Auth', authPlugin);
    },
  };
}

export default {
  init,
  routeGuard,
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment