├── .browserslistrc ├── .env ├── .env.development ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico ├── index.html └── logo.png ├── src ├── App.vue ├── assets │ ├── loading.svg │ └── logo.png ├── auth │ └── index.ts ├── components │ ├── Error.vue │ ├── Hero.vue │ ├── HomeContent.vue │ └── NavBar.vue ├── main.ts ├── plugins │ └── element.js ├── router │ └── index.ts ├── shims-vue.d.ts └── views │ ├── ExternalApi.vue │ ├── Home.vue │ └── Profile.vue ├── tsconfig.json └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_NAME="myapp" 2 | VUE_APP_API_URL=/api 3 | VUE_APP_AUTH0_DOMAIN="" 4 | VUE_APP_AUTH0_AUDIENCE="" 5 | VUE_APP_AUTH0_CLIENT_KEY="" 6 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=http://localhost:5000/api 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auth0 sample with Vue 3 and TypeScript 2 | 3 | The original code is [from here](https://gist.github.com/K3TH3R/416e5c6627436fa87db16f57f50496d1). 4 | 5 | I added the following: 6 | - TypeScript refactoring 7 | - Proper registration 8 | - Fixed all the demo components with `inject` 9 | - Fixed the router guards 10 | 11 | Auth0 configuration should be provided in the `.env` file. 12 | 13 | There's no web server included here as I don't use NodeJS. You can call any server, which listens on `http://localhost:5000/api` 14 | 15 | ## Project setup 16 | ``` 17 | yarn install 18 | ``` 19 | 20 | ### Compiles and hot-reloads for development 21 | ``` 22 | yarn serve 23 | ``` 24 | 25 | ### Compiles and minifies for production 26 | ``` 27 | yarn build 28 | ``` 29 | 30 | ### Customize configuration 31 | See [Configuration Reference](https://cli.vuejs.org/config/). 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --port 3000", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.21.1", 11 | "core-js": "^3.6.5", 12 | "element-plus": "^1.0.2-beta.28", 13 | "vue": "^3.0.0", 14 | "vue-router": "^4.0.0-0", 15 | "vuex": "^4.0.0-0", 16 | "@auth0/auth0-spa-js": "^1.13.6", 17 | "@fortawesome/fontawesome-svg-core": "^1.2.32", 18 | "@fortawesome/free-solid-svg-icons": "^5.15.1", 19 | "@fortawesome/vue-fontawesome": "3.0.0-3" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "~4.5.0", 23 | "@vue/cli-plugin-router": "~4.5.0", 24 | "@vue/cli-plugin-typescript": "~4.5.0", 25 | "@vue/cli-plugin-vuex": "~4.5.0", 26 | "@vue/cli-service": "~4.5.0", 27 | "@vue/compiler-sfc": "^3.0.0", 28 | "sass": "^1.32.7", 29 | "sass-loader": "7.3.1", 30 | "stylus": "^0.54.7", 31 | "stylus-loader": "^3.0.2", 32 | "typescript": "~3.9.3", 33 | "vue-cli-plugin-element-plus": "~0.0.13" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexeyzimarev/auth0-vue3-ts/d87d45323319239b059712a014c8d033b7d2c6b5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 02 - Calling an API 9 | 10 | 16 | 20 | 21 | 22 | 28 |
29 | 30 | 31 | 36 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexeyzimarev/auth0-vue3-ts/d87d45323319239b059712a014c8d033b7d2c6b5/public/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /src/assets/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexeyzimarev/auth0-vue3-ts/d87d45323319239b059712a014c8d033b7d2c6b5/src/assets/logo.png -------------------------------------------------------------------------------- /src/auth/index.ts: -------------------------------------------------------------------------------- 1 | import createAuth0Client, { 2 | Auth0Client, 3 | GetIdTokenClaimsOptions, 4 | GetTokenSilentlyOptions, 5 | GetTokenWithPopupOptions, 6 | LogoutOptions, 7 | RedirectLoginOptions, 8 | User 9 | } from '@auth0/auth0-spa-js' 10 | import {App, Plugin, computed, reactive, watchEffect} from 'vue' 11 | import {NavigationGuardWithThis} from "vue-router"; 12 | 13 | let client: Auth0Client; 14 | 15 | interface Auth0PluginState { 16 | loading: boolean, 17 | isAuthenticated: boolean; 18 | user: User | undefined, 19 | popupOpen: boolean; 20 | error: any 21 | } 22 | 23 | const state = reactive({ 24 | loading: true, 25 | isAuthenticated: false, 26 | user: {}, 27 | popupOpen: false, 28 | error: null, 29 | }) 30 | 31 | async function handleRedirectCallback() { 32 | state.loading = true; 33 | 34 | try { 35 | await client.handleRedirectCallback(); 36 | state.user = await client.getUser(); 37 | state.isAuthenticated = true; 38 | } catch (e) { 39 | state.error = e; 40 | } finally { 41 | state.loading = false; 42 | } 43 | } 44 | 45 | function loginWithRedirect(o: RedirectLoginOptions) { 46 | return client.loginWithRedirect(o); 47 | } 48 | 49 | function getIdTokenClaims(o: GetIdTokenClaimsOptions) { 50 | return client.getIdTokenClaims(o); 51 | } 52 | 53 | function getTokenSilently(o: GetTokenSilentlyOptions) { 54 | return client.getTokenSilently(o); 55 | } 56 | 57 | function getTokenWithPopup(o: GetTokenWithPopupOptions) { 58 | return client.getTokenWithPopup(o); 59 | } 60 | 61 | function logout(o: LogoutOptions) { 62 | return client.logout(o); 63 | } 64 | 65 | const authPlugin = { 66 | isAuthenticated: computed(() => state.isAuthenticated), 67 | loading: computed(() => state.loading), 68 | user: computed(() => state.user), 69 | getIdTokenClaims, 70 | getTokenSilently, 71 | getTokenWithPopup, 72 | handleRedirectCallback, 73 | loginWithRedirect, 74 | logout, 75 | } 76 | 77 | const routeGuard: NavigationGuardWithThis = (to: any, from: any, next: any) => { 78 | const {isAuthenticated, loading, loginWithRedirect} = authPlugin; 79 | 80 | const verify = async () => { 81 | // If the user is authenticated, continue with the route 82 | if (isAuthenticated.value) { 83 | return next(); 84 | } 85 | 86 | // Otherwise, log in 87 | await loginWithRedirect({appState: {targetUrl: to.fullPath}}); 88 | } 89 | 90 | // If loading has already finished, check our auth state using `fn()` 91 | if (!loading.value) { 92 | return verify(); 93 | } 94 | 95 | // Watch for the loading property to change before we check isAuthenticated 96 | watchEffect(() => { 97 | if (!loading.value) { 98 | return verify(); 99 | } 100 | }) 101 | } 102 | 103 | interface Auth0PluginOptions { 104 | domain: string, 105 | clientId: string, 106 | audience: string, 107 | redirectUri: string, 108 | 109 | onRedirectCallback(appState: any): void 110 | } 111 | 112 | async function init(options: Auth0PluginOptions): Promise { 113 | client = await createAuth0Client({ 114 | // domain: process.env.VUE_APP_AUTH0_DOMAIN, 115 | // client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY, 116 | domain: options.domain, 117 | client_id: options.clientId, 118 | audience: options.audience, 119 | redirect_uri: options.redirectUri, 120 | }); 121 | 122 | try { 123 | // If the user is returning to the app after authentication 124 | if ( 125 | window.location.search.includes('code=') && 126 | window.location.search.includes('state=') 127 | ) { 128 | // handle the redirect and retrieve tokens 129 | const {appState} = await client.handleRedirectCallback(); 130 | 131 | // Notify subscribers that the redirect callback has happened, passing the appState 132 | // (useful for retrieving any pre-authentication state) 133 | options.onRedirectCallback(appState); 134 | } 135 | } catch (e) { 136 | state.error = e; 137 | } finally { 138 | // Initialize our internal authentication state 139 | state.isAuthenticated = await client.isAuthenticated(); 140 | state.user = await client.getUser(); 141 | state.loading = false; 142 | } 143 | 144 | return { 145 | install: (app: App) => { 146 | app.provide('Auth', authPlugin); 147 | }, 148 | } 149 | } 150 | 151 | interface Auth0Plugin { 152 | init(options: Auth0PluginOptions): Promise; 153 | routeGuard: NavigationGuardWithThis 154 | } 155 | 156 | export const Auth0: Auth0Plugin = { 157 | init, 158 | routeGuard 159 | } 160 | -------------------------------------------------------------------------------- /src/components/Error.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/Hero.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/components/HomeContent.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 65 | -------------------------------------------------------------------------------- /src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 124 | 125 | 131 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {createApp} from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import {Auth0} from "@/auth"; 5 | 6 | import { library } from "@fortawesome/fontawesome-svg-core"; 7 | import { faLink, faUser, faPowerOff } from "@fortawesome/free-solid-svg-icons"; 8 | import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; 9 | 10 | async function init() { 11 | const AuthPlugin = await Auth0.init({ 12 | onRedirectCallback: (appState) => { 13 | router.push( 14 | appState && appState.targetUrl 15 | ? appState.targetUrl 16 | : window.location.pathname, 17 | ) 18 | }, 19 | clientId: process.env.VUE_APP_AUTH0_CLIENT_KEY, 20 | domain: process.env.VUE_APP_AUTH0_DOMAIN, 21 | audience: process.env.VUE_APP_AUTH0_AUDIENCE, 22 | redirectUri: window.location.origin, 23 | }); 24 | const app = createApp(App); 25 | library.add(faLink, faUser, faPowerOff); 26 | app 27 | .use(AuthPlugin) 28 | .use(router) 29 | .component("font-awesome-icon", FontAwesomeIcon) 30 | .mount('#app'); 31 | } 32 | 33 | init(); 34 | -------------------------------------------------------------------------------- /src/plugins/element.js: -------------------------------------------------------------------------------- 1 | import ElementPlus from 'element-plus' 2 | import 'element-plus/lib/theme-chalk/index.css' 3 | 4 | export default (app) => { 5 | app.use(ElementPlus) 6 | } 7 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; 2 | import Home from "../views/Home.vue"; 3 | import Profile from "../views/Profile.vue"; 4 | import ExternalApi from "../views/ExternalApi.vue"; 5 | import {Auth0} from "@/auth"; 6 | 7 | const routes: Array = [ 8 | { 9 | path: "/", 10 | name: "home", 11 | component: Home 12 | }, 13 | { 14 | path: "/profile", 15 | name: "profile", 16 | component: Profile, 17 | beforeEnter: Auth0.routeGuard 18 | }, 19 | { 20 | path: "/external-api", 21 | component: ExternalApi, 22 | beforeEnter: Auth0.routeGuard 23 | } 24 | ] 25 | 26 | const router = createRouter({ 27 | history: createWebHistory(process.env.BASE_URL), 28 | routes 29 | }) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/views/ExternalApi.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 55 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | --------------------------------------------------------------------------------