├── .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 |
23 | We're sorry but 02 - Calling an API doesn't work properly without
25 | JavaScript enabled. Please enable it to continue.
27 |
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 |
2 |
3 |
4 |
10 |
11 |
12 |
13 | Sample project provided by
14 | Auth0
15 |
16 |
17 |
18 |
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 |
2 |
3 | {{ msg }}
4 |
5 | ×
6 |
7 |
8 |
9 |
10 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/Hero.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Vue.js Sample Project
5 |
6 | This is a sample application that demonstrates an authentication flow for an SPA, using
7 | Vue.js
10 |
11 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/src/components/HomeContent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
What can I do next?
4 |
5 |
6 |
7 |
13 |
Auth0 supports social providers as Facebook, Twitter, Instagram and 100+, Enterprise providers as Microsoft
14 | Office 365, Google Apps, Azure, and more. You can also use any OAuth2 Authorization Server.
15 |
16 |
17 |
18 |
19 |
20 |
26 |
Add an extra layer of security by enabling Multi-factor Authentication, requiring your users to provide more
27 | than one piece of identifying information. Push notifications, authenticator apps, SMS, and DUO Security are
28 | supported.
29 |
30 |
31 |
32 |
33 |
34 |
40 |
Auth0 can detect anomalies and stop malicious attempts to access your application. Anomaly detection can
41 | alert you and your users of suspicious activity, as well as block further login attempts.
42 |
43 |
44 |
45 |
46 |
47 |
53 |
Rules are JavaScript functions that execute when a user authenticates to your application. They run once the
54 | authentication process is complete, and you can use them to customize and extend Auth0's capabilities.
55 |
56 |
57 |
58 |
59 |
60 |
65 |
--------------------------------------------------------------------------------
/src/components/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Home
22 |
23 |
24 | External API
25 |
26 |
27 |
28 |
29 | Login
34 |
35 |
36 |
37 |
38 |
44 |
50 |
51 |
62 |
63 |
64 |
65 |
68 |
69 |
74 |
75 |
76 |
82 | {{ user.name }}
83 |
84 |
85 |
86 |
87 | Profile
88 |
89 |
90 |
91 |
92 | Log out
93 |
94 |
95 |
96 |
97 |
98 |
99 |
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 |
2 |
3 |
4 |
External API
5 |
6 | Call an external API by clicking the button below. This will call the external API using an access token, and
7 | the API will validate it using
8 | the API's audience value.
9 |
10 |
11 |
Call API
12 |
13 |
14 |
15 |
16 |
Result
17 | {{ JSON.stringify(apiMessage, null, 2) }}
18 |
19 |
20 |
21 |
22 |
23 |
55 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
21 |
22 |
29 |
--------------------------------------------------------------------------------
/src/views/Profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 | {{ JSON.stringify(user, null, 2) }}
19 |
20 |
21 |
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 |
--------------------------------------------------------------------------------