- Add login to your application
- Display the user profile
- Add logout to your application
- Calling an API
- Accessing ID Token claims
- Error Handling
- Protecting a route
- Protecting a route when using multiple Vue applications
- Accessing Auth0Client outside of a component
- Organizations
- Device-bound tokens with DPoP
- Multi-Factor Authentication (MFA)
- Step-Up Authentication
- Custom Token Exchange
In order to add login to your application you can use the loginWithRedirect function that is exposed on the return value of useAuth0, which you can access in your component's setup function.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithRedirect } = useAuth0();
return {
login: () => {
loginWithRedirect();
}
};
}
};
</script>Once setup returns the correct method, you can call that method from your component's HTML.
<template>
<div>
<button @click="login">Log in</button>
</div>
</template>Using Options API
<template>
<div>
<button @click="login">Log in</button>
</div>
</template>
<script>
export default {
methods: {
login() {
this.$auth0.loginWithRedirect();
}
}
};
</script>To display the user's information, you can use the reactive user property exposed by the return value of useAuth0, which you can access in your component's setup function.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithRedirect, user } = useAuth0();
return {
login: () => {
loginWithRedirect();
},
user
};
}
};
</script>Once setup returns the SDK's reactive property, you can access that property from your component's HTML.
<template>
<div>
<h2>User Profile</h2>
<button @click="login">Log in</button>
<pre>
<code>{{ user }}</code>
</pre>
</div>
</template>Note: Ensure the user is authenticated by implementing login in your application before accessing the user's profile.
Using Options API
<template>
<div>
<h2>User Profile</h2>
<button @click="login">Log in</button>
<pre>
<code>{{ user }}</code>
</pre>
</div>
</template>
<script>
export default {
data: function () {
return {
user: this.$auth0.user
};
},
methods: {
login() {
this.$auth0.loginWithRedirect();
}
}
};
</script>Adding logout to your application you be done by using the logout function that is exposed on the return value of useAuth0, which you can access in your component's setup function.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { logout } = useAuth0();
return {
logout: () => {
logout({ logoutParams: { returnTo: window.location.origin } });
}
};
}
};
</script>Once setup returns the correct method, you can call that method from your component's HTML.
<template>
<div>
<button @click="logout">Log out</button>
</div>
</template>Using Options API
<template>
<div>
<button @click="logout">Log out</button>
</div>
</template>
<script>
export default {
methods: {
logout() {
this.$auth0.logout({
logoutParams: { returnTo: window.location.origin }
});
}
}
};
</script>To call an API, configure the plugin by setting the audience to the API Identifier of the API in question:
import { createAuth0 } from '@auth0/auth0-vue';
const app = createApp(App);
app.use(
createAuth0({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>',
audience: '<AUTH0_AUDIENCE>'
}
})
);
app.mount('#app');After configuring the plugin, you will need to retrieve an Access Token and set it on the Authorization header of your request.
Retrieving an Access Token can be done by using the getAccessTokenSilently function that is exposed on the return value of useAuth0, which you can access in your component's setup function.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { getAccessTokenSilently } = useAuth0();
return {
doSomethingWithToken: async () => {
const token = await getAccessTokenSilently();
const response = await fetch('https://api.example.com/posts', {
headers: {
Authorization: `Bearer ${token}`
}
});
const data = await response.json();
}
};
}
};
</script>Using Options API
<script>
export default {
methods: {
async doSomethingWithToken() {
const token = await this.$auth0.getAccessTokenSilently();
const response = await fetch('https://api.example.com/posts', {
headers: {
Authorization: `Bearer ${token}`
}
});
const data = await response.json();
}
}
};
</script>To get access to the user's claims, you can use the reactive idTokenClaims property exposed by the return value of useAuth0, which you can access in your component's setup function.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithRedirect, idTokenClaims } = useAuth0();
return {
login: () => {
loginWithRedirect();
},
idTokenClaims
};
}
};
</script>Once setup returns the SDK's reactive property, you can access that property from your component's HTML.
<template>
<div>
<h2>ID Token Claims</h2>
<button @click="login">Log in</button>
<pre>
<code>{{ idTokenClaims }}</code>
</pre>
</div>
</template>Using Options API
<template>
<div>
<h2>ID Token Claims</h2>
<button @click="login">Log in</button>
<pre>
<code>{{ idTokenClaims }}</code>
</pre>
</div>
</template>
<script>
export default {
data: function () {
return {
idTokenClaims: this.$auth0.idTokenClaims
};
},
methods: {
login() {
this.$auth0.loginWithRedirect();
}
}
};
</script>When using this SDK, it could be the case that it is unable to correctly handle the authentication flow for a variety of reasons (e.g. an expired session with Auth0 when trying to get a token silently). In these situations, calling the actual methods will result in an exception being thrown (e.g. login_required). On top of that, these errors are made available through the SDK's reactive error property:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { error } = useAuth0();
return {
error
};
}
};
</script>Once setup returns the SDK's error property, you can access that property from your component's HTML.
<template>
<div>
<h2>Error Handling</h2>
<pre>
<code>{{ error?.error }}</code>
</pre>
</div>
</template>Using Options API
<template>
<div>
<h2>Error Handling</h2>
<pre>
<code>{{ error?.error }}</code>
</pre>
</div>
</template>
<script>
export default {
data: function () {
return {
error: this.$auth0.error
};
}
};
</script>If you are using our Auth0-Vue SDK with Vue-Router, you can protect a route by using the Navigation Guard provided by the SDK.
⚠️ Note: the order in which the Router and Auth0 Vue plugin are registered is important. You must register the Router before the Auth0 SDK or you might see unexpected behavior.
import { createApp } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import { createAuth0, authGuard } from '@auth0/auth0-vue';
const app = createApp(App);
app.use(createRouter({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/profile',
name: 'profile',
component: Profile,
beforeEnter: authGuard
}
],
history: createWebHashHistory()
}));
app.use(createAuth0({ ... }));
app.mount('#app');Applying the guard to a route, as shown above, will only allow access to authenticated users. When a non-authenticated user tries to access a protected route, the SDK will redirect the user to Auth0 and redirect them back to your application's redirect_uri (which is configured in createAuth0, see Configuring the plugin). Once the SDK is done processing the response from Auth0 and exchanging it for tokens, the SDK will redirect the user back to the protected route they were trying to access initially.
⚠️ If you are using multiple Vue applications with our SDK on a single page, using the above guard does not support a situation where the Auth0 Domain and ClientID would be different. In that case, read our guide on protecting a route when using multiple Vue applications.
When using multiple Vue applications that use their own version of the Auth0Plugin (using a different Domain and/or Client ID), an instance of the Vue Application needs to be passed down to the createAuthGuard() function exposed by the SDK.
import { createApp } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import { createAuth0, createAuthGuard } from '@auth0/auth0-vue';
import App from './App.vue';
import Home from './components/Home.vue';
import Profile from './components/Profile.vue';
const app = createApp(App);
app.use(
createRouter({
linkActiveClass: 'btn-primary',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/profile',
name: 'profile',
component: Profile,
beforeEnter: createAuthGuard(app)
}
],
history: createWebHashHistory()
})
);
app.use(
createAuth0({
domain,
clientId
})
);
app.mount('#app');Doing the above ensures every guard is connected to the Auth0Plugin that's configured in the same Vue application.
To be able to access Auth0Client outside of the component, there are a couple of things you need to do.
First of all, start with moving the creation of the plugin to an external file:
export const auth0 = createAuth0({ ... });Next, you can import the exported plugin instance when configuring the Vue app.
import { auth0 } from './auth0';
createApp(App).use(auth0).mount('#app');However, you can now also import the exported plugin instance anywhere else and access it's methods
import { auth0 } from './auth0';
export async function getAccessTokenSilentlyOutsideComponent(options) {
return auth0.getAccessTokenSilently(options);
}This would allow you to interact with our SDK from outside of components, such as Axios interceptors.
Note: Be aware that none of the above is specific to our SDK, but would translate to any plugin in Vue.
Organizations is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications.
Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans.
Log in to an organization by specifying the organization parameter when registering the plugin:
app.use(
createAuth0({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>',
organization: 'YOUR_ORGANIZATION_ID_OR_NAME'
}
})
);You can also specify the organization when logging in:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithRedirect, loginWithPopup } = useAuth0();
return {
login: () => {
// Using a redirect
loginWithRedirect({
authorizationParams: {
organization: 'YOUR_ORGANIZATION_ID_OR_NAME',
}
});
// Using a popup window
loginWithPopup({
authorizationParams: {
organization: 'YOUR_ORGANIZATION_ID_OR_NAME',
}
})
}
};
}
};
</script>Accept a user invitation through the SDK by creating a route within your application that can handle the user invitation URL, and log the user in by passing the organization and invitation parameters from this URL. You can either use loginWithRedirect or loginWithPopup as needed.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithRedirect, loginWithPopup } = useAuth0();
const route = useRoute();
const { organization, invitation } = route.params;
return {
login: () => {
// Using a redirect
loginWithRedirect({
authorizationParams: {
organization,
invitation,
}
});
// Using a popup window
loginWithPopup({
authorizationParams: {
organization,
invitation,
}
})
}
};
}
};
</script>Demonstrating Proof-of-Possession —or simply DPoP— is a recent OAuth 2.0 extension defined in RFC9449.
It defines a mechanism for securely binding tokens to a specific device using cryptographic signatures. Without it, a token leak caused by XSS or other vulnerabilities could allow an attacker to impersonate the real user.
To support DPoP in auth0-vue, some APIs available in modern browsers are required:
-
Crypto API: allows to create and use cryptographic keys, which are used to generate the proofs (i.e. signatures) required for DPoP.
-
IndexedDB: enables the use of cryptographic keys without exposing the private material.
The following OAuth 2.0 flows are currently supported by auth0-vue:
-
Authorization Code Flow (
authorization_code). -
Refresh Token Flow (
refresh_token).
Important
Currently, only the ES256 algorithm is supported.
DPoP is disabled by default. To enable it, set the useDpop option to true when configuring the plugin. For example:
import { createAuth0 } from '@auth0/auth0-vue';
const app = createApp(App);
app.use(
createAuth0({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
useDpop: true, // 👈
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>',
},
})
);
app.mount('#app');After enabling DPoP, every new session using a supported OAuth 2.0 flow in Auth0 will begin transparently to use tokens that are cryptographically bound to the current browser.
Important
DPoP will only be used for new user sessions created after enabling it. Any previously existing sessions will continue using non-DPoP tokens until the user logs in again.
You decide how to handle this transition. For example, you might require users to log in again the next time they use your application.
Note
Using DPoP requires storing some temporary data in the user's browser. When you log the user out with logout(), this data is deleted.
Tip
If all your clients are already using DPoP, you may want to increase security by making Auth0 reject any non-DPoP interactions. See the docs on Sender Constraining for details.
You use a DPoP token the same way as a "traditional" access token, except it must be sent to the server with an Authorization: DPoP <token> header instead of the usual Authorization: Bearer <token>.
For internal requests sent by auth0-vue to Auth0, simply enable the useDpop option and every interaction with Auth0 will be protected.
However, to use DPoP with a custom, external API, some additional work is required. The SDK provides some low-level methods to help with this:
getDpopNonce()setDpopNonce()generateDpopProof()
However, due to the nature of how DPoP works, this is not a trivial task:
- When a nonce is missing or expired, the request may need to be retried.
- Received nonces must be stored and managed.
- DPoP headers must be generated and included in every request, and regenerated for retries.
Because of this, we recommend using the provided createFetcher() method with fetchWithAuth(), which handles all of this for you.
The fetchWithAuth() method is a drop-in replacement for the native fetch() function from the Fetch API, so if you're already using it, the change will be minimal.
For example, if you had this code:
const response = await fetch('https://api.example.com/foo', {
method: 'GET',
headers: { 'user-agent': 'My Client 1.0' },
});
console.log(response.status);
console.log(response.headers);
console.log(await response.json());You would change it as follows:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { createFetcher } = useAuth0();
return {
fetchData: async () => {
const fetcher = createFetcher({
dpopNonceId: 'my_api_request',
});
const response = await fetcher.fetchWithAuth('https://api.example.com/foo', {
method: 'GET',
headers: { 'user-agent': 'My Client 1.0' },
});
console.log(response.status);
console.log(response.headers);
console.log(await response.json());
},
};
},
};
</script>Using Options API
<script>
export default {
methods: {
async fetchData() {
const fetcher = this.$auth0.createFetcher({
dpopNonceId: 'my_api_request',
});
const response = await fetcher.fetchWithAuth('https://api.example.com/foo', {
method: 'GET',
headers: { 'user-agent': 'My Client 1.0' },
});
console.log(response.status);
console.log(response.headers);
console.log(await response.json());
},
},
};
</script>When using fetchWithAuth(), the following will be handled for you automatically:
- Use
getAccessTokenSilently()to get the access token to inject in the headers. - Generate and inject DPoP headers when needed.
- Store and update any DPoP nonces.
- Handle retries caused by a rejected nonce.
Important
If DPoP is enabled, a dpopNonceId must be present in the createFetcher() parameters, since it's used to keep track of the DPoP nonces for each request.
If you need something more complex than the example above, you can provide a custom implementation in the fetch property.
However, since auth0-vue needs to make decisions based on HTTP responses, your implementation must return an object with at least two properties:
status: the response status code as a number.headers: the response headers as a plain object or as a Fetch API's Headers-like interface.
Whatever it returns, it will be passed as the output of the fetchWithAuth() method.
Your implementation will be called with a standard, ready-to-use Request object, which will contain any headers needed for authorization and DPoP usage (if enabled). Depending on your needs, you can use this object directly or treat it as a container with everything required to make the request your own way.
If you need to make requests to different endpoints of the same API, passing a baseUrl to createFetcher() can be useful:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { createFetcher } = useAuth0();
const fetcher = createFetcher({
dpopNonceId: 'my-api',
baseUrl: 'https://api.example.com',
});
return {
getFoo: async () => fetcher.fetchWithAuth('/foo'), // => https://api.example.com/foo
getBar: async () => fetcher.fetchWithAuth('/bar'), // => https://api.example.com/bar
getXyz: async () => fetcher.fetchWithAuth('/xyz'), // => https://api.example.com/xyz
// If the passed URL is absolute, `baseUrl` will be ignored for convenience:
getFromOtherApi: async () => fetcher.fetchWithAuth('https://other-api.example.com/foo'),
};
},
};
</script>Using Options API
<script>
export default {
data() {
return {
fetcher: this.$auth0.createFetcher({
dpopNonceId: 'my-api',
baseUrl: 'https://api.example.com',
}),
};
},
methods: {
async getFoo() {
return this.fetcher.fetchWithAuth('/foo'); // => https://api.example.com/foo
},
async getBar() {
return this.fetcher.fetchWithAuth('/bar'); // => https://api.example.com/bar
},
async getXyz() {
return this.fetcher.fetchWithAuth('/xyz'); // => https://api.example.com/xyz
},
async getFromOtherApi() {
// If the passed URL is absolute, `baseUrl` will be ignored for convenience:
return this.fetcher.fetchWithAuth('https://other-api.example.com/foo');
},
},
};
</script>When working with multiple APIs, create separate fetchers for each. Each fetcher manages its own nonces independently:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { createFetcher } = useAuth0();
// Create separate fetchers for different APIs
const internalApi = createFetcher({
dpopNonceId: 'internal-api',
baseUrl: 'https://internal.example.com',
});
const partnerApi = createFetcher({
dpopNonceId: 'partner-api',
baseUrl: 'https://partner.example.com',
});
return {
fetchInternalData: async () => {
const response = await internalApi.fetchWithAuth('/data');
return response.json();
},
fetchPartnerData: async () => {
const response = await partnerApi.fetchWithAuth('/resources');
return response.json();
},
fetchAllData: async () => {
const [internal, partner] = await Promise.all([
internalApi.fetchWithAuth('/data'),
partnerApi.fetchWithAuth('/resources'),
]);
return {
internal: await internal.json(),
partner: await partner.json(),
};
},
};
},
};
</script>Using Options API
<script>
export default {
data() {
return {
internalApi: this.$auth0.createFetcher({
dpopNonceId: 'internal-api',
baseUrl: 'https://internal.example.com',
}),
partnerApi: this.$auth0.createFetcher({
dpopNonceId: 'partner-api',
baseUrl: 'https://partner.example.com',
}),
};
},
methods: {
async fetchInternalData() {
const response = await this.internalApi.fetchWithAuth('/data');
return response.json();
},
async fetchPartnerData() {
const response = await this.partnerApi.fetchWithAuth('/resources');
return response.json();
},
async fetchAllData() {
const [internal, partner] = await Promise.all([
this.internalApi.fetchWithAuth('/data'),
this.partnerApi.fetchWithAuth('/resources'),
]);
return {
internal: await internal.json(),
partner: await partner.json(),
};
},
},
};
</script>The fetcher supports all HTTP methods and automatically includes DPoP proofs:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { createFetcher } = useAuth0();
const fetcher = createFetcher({
dpopNonceId: 'my-api',
baseUrl: 'https://api.example.com',
});
return {
createPost: async (postData) => {
const response = await fetcher.fetchWithAuth('/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
return response.json();
},
updatePost: async (postId, postData) => {
const response = await fetcher.fetchWithAuth(`/posts/${postId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
return response.json();
},
deletePost: async (postId) => {
await fetcher.fetchWithAuth(`/posts/${postId}`, {
method: 'DELETE',
});
},
};
},
};
</script>Using Options API
<script>
export default {
data() {
return {
fetcher: this.$auth0.createFetcher({
dpopNonceId: 'my-api',
baseUrl: 'https://api.example.com',
}),
};
},
methods: {
async createPost(postData) {
const response = await this.fetcher.fetchWithAuth('/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
return response.json();
},
async updatePost(postId, postData) {
const response = await this.fetcher.fetchWithAuth(`/posts/${postId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
return response.json();
},
async deletePost(postId) {
await this.fetcher.fetchWithAuth(`/posts/${postId}`, {
method: 'DELETE',
});
},
},
};
</script>For scenarios requiring full control over DPoP proof generation and nonce management, you can use the low-level methods:
<script>
import { useAuth0, UseDpopNonceError } from '@auth0/auth0-vue';
export default {
setup() {
const { getAccessTokenSilently, getDpopNonce, setDpopNonce, generateDpopProof } = useAuth0();
return {
manualDpopRequest: async () => {
try {
// 1. Get access token
const token = await getAccessTokenSilently();
// 2. Get DPoP nonce for the API
const nonce = await getDpopNonce('my-api');
// 3. Generate DPoP proof
const proof = await generateDpopProof({
url: 'https://api.example.com/data',
method: 'POST',
accessToken: token,
nonce,
});
// 4. Make request with DPoP headers
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
Authorization: `DPoP ${token}`,
DPoP: proof,
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'example' }),
});
// 5. Update nonce if server provides a new one
const newNonce = response.headers.get('DPoP-Nonce');
if (newNonce) {
await setDpopNonce(newNonce, 'my-api');
}
return response.json();
} catch (error) {
if (error instanceof UseDpopNonceError) {
console.error('DPoP nonce validation failed:', error.message);
}
throw error;
}
},
};
},
};
</script>Using Options API
<script>
import { UseDpopNonceError } from '@auth0/auth0-vue';
export default {
methods: {
async manualDpopRequest() {
try {
// 1. Get access token
const token = await this.$auth0.getAccessTokenSilently();
// 2. Get DPoP nonce for the API
const nonce = await this.$auth0.getDpopNonce('my-api');
// 3. Generate DPoP proof
const proof = await this.$auth0.generateDpopProof({
url: 'https://api.example.com/data',
method: 'POST',
accessToken: token,
nonce,
});
// 4. Make request with DPoP headers
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
Authorization: `DPoP ${token}`,
DPoP: proof,
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'example' }),
});
// 5. Update nonce if server provides a new one
const newNonce = response.headers.get('DPoP-Nonce');
if (newNonce) {
await this.$auth0.setDpopNonce(newNonce, 'my-api');
}
return response.json();
} catch (error) {
if (error instanceof UseDpopNonceError) {
console.error('DPoP nonce validation failed:', error.message);
}
throw error;
}
},
},
};
</script>The mfa property on the object returned by useAuth0 gives access to all MFA operations. MFA flows are triggered when getAccessTokenSilently throws a MfaRequiredError.
Prerequisites: MFA requires refresh token rotation to be enabled in the Auth0 client configuration (
useRefreshTokens: true).
Configure your Auth0 client with refresh tokens:
import { createAuth0 } from '@auth0/auth0-vue';
const app = createApp(App);
app.use(
createAuth0({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: window.location.origin
},
useRefreshTokens: true
})
);When getAccessTokenSilently results in an MFA requirement, it throws MfaRequiredError. Check mfa_requirements.enroll first — if it is non-empty the user must enroll a new factor before they can authenticate; otherwise proceed with a challenge against an existing authenticator:
<script>
import { useAuth0, MfaRequiredError } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { getAccessTokenSilently, mfa } = useAuth0();
const mfaToken = ref(null);
const fetchToken = async () => {
try {
const token = await getAccessTokenSilently({
authorizationParams: { audience: 'https://api.example.com' }
});
console.log('Access token:', token);
} catch (e) {
if (e instanceof MfaRequiredError) {
mfaToken.value = e.mfa_token;
if (e.mfa_requirements?.enroll?.length) {
// User has no authenticators yet — show enrollment UI
const factors = await mfa.getEnrollmentFactors(e.mfa_token);
// present factors to the user (see Enrollment Flow below)
} else {
// User is already enrolled — show challenge UI
const authenticators = await mfa.getAuthenticators(e.mfa_token);
// present authenticators to the user (see Challenge Flow below)
}
}
}
};
return { fetchToken, mfaToken };
}
};
</script>Use the challenge flow when the user already has an enrolled authenticator.
Note:
mfa.verify()returns raw tokens but does not automatically update Vue's reactive state (isAuthenticated,user,idTokenClaims). CallcheckSession()after a successfulverify()to refresh the auth state in your components.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { mfa, checkSession } = useAuth0();
const otpCode = ref('');
const verifyOtp = async (mfaToken) => {
// For OTP, no challenge call is required — the user enters
// the code directly from their authenticator app.
await mfa.verify({
mfaToken,
otp: otpCode.value
});
// Refresh Vue reactive state (isAuthenticated, user, etc.)
await checkSession();
};
return { otpCode, verifyOtp };
}
};
</script><script>
import { useAuth0 } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { mfa, checkSession } = useAuth0();
const bindingCode = ref('');
const oobCode = ref('');
const sendChallenge = async (mfaToken, authenticatorId) => {
const challenge = await mfa.challenge({
mfaToken,
challengeType: 'oob',
authenticatorId
});
oobCode.value = challenge.oobCode;
};
const verifyOob = async (mfaToken) => {
await mfa.verify({
mfaToken,
oobCode: oobCode.value,
bindingCode: bindingCode.value
});
// Refresh Vue reactive state (isAuthenticated, user, etc.)
await checkSession();
};
return { bindingCode, oobCode, sendChallenge, verifyOob };
}
};
</script><script>
import { useAuth0 } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { mfa, checkSession } = useAuth0();
const recoveryCode = ref('');
const verifyRecoveryCode = async (mfaToken) => {
const tokens = await mfa.verify({
mfaToken,
recoveryCode: recoveryCode.value
});
// Auth0 rotates the recovery code on use — save the replacement or the
// user will be locked out if they need to fall back again.
if (tokens.recovery_code) {
console.warn('Save your new recovery code:', tokens.recovery_code);
}
// Refresh Vue reactive state (isAuthenticated, user, etc.)
await checkSession();
};
return { recoveryCode, verifyRecoveryCode };
}
};
</script>Use the enrollment flow when the user has no authenticators yet. Call getEnrollmentFactors to discover what they can enroll in.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { mfa } = useAuth0();
const enrollmentFactors = ref([]);
const loadEnrollmentOptions = async (mfaToken) => {
enrollmentFactors.value = await mfa.getEnrollmentFactors(mfaToken);
// e.g. [{ type: 'otp' }, { type: 'phone' }, { type: 'push-notification' }]
};
return { enrollmentFactors, loadEnrollmentOptions };
}
};
</script><script>
import { useAuth0 } from '@auth0/auth0-vue';
import { ref } from 'vue';
export default {
setup() {
const { mfa } = useAuth0();
const barcodeUri = ref('');
const secret = ref('');
const enrollTotp = async (mfaToken) => {
const enrollment = await mfa.enroll({
mfaToken,
factorType: 'otp'
});
barcodeUri.value = enrollment.barcodeUri; // Render as QR code
secret.value = enrollment.secret; // Show as manual entry fallback
};
return { barcodeUri, secret, enrollTotp };
}
};
</script><script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { mfa } = useAuth0();
const enrollSms = async (mfaToken, phoneNumber) => {
await mfa.enroll({
mfaToken,
factorType: 'sms',
phoneNumber // E.164 format, e.g. '+12025551234'
});
// An OOB code is sent via SMS — prompt the user to enter it
};
return { enrollSms };
}
};
</script>The mfa client throws typed errors for each operation:
<script>
import {
useAuth0,
MfaRequiredError,
MfaListAuthenticatorsError,
MfaEnrollmentError,
MfaChallengeError,
MfaVerifyError,
MfaEnrollmentFactorsError
} from '@auth0/auth0-vue';
export default {
setup() {
const { getAccessTokenSilently, mfa } = useAuth0();
const handleMfaFlow = async () => {
try {
await getAccessTokenSilently();
} catch (e) {
if (!(e instanceof MfaRequiredError)) throw e;
try {
const authenticators = await mfa.getAuthenticators(e.mfa_token);
// ... drive challenge/enrollment UI
} catch (mfaError) {
if (mfaError instanceof MfaListAuthenticatorsError) {
console.error('Failed to list authenticators:', mfaError.error_description);
} else if (mfaError instanceof MfaVerifyError) {
console.error('Verification failed:', mfaError.error_description);
} else if (mfaError instanceof MfaEnrollmentError) {
console.error('Enrollment failed:', mfaError.error_description);
} else if (mfaError instanceof MfaChallengeError) {
console.error('Challenge failed:', mfaError.error_description);
} else if (mfaError instanceof MfaEnrollmentFactorsError) {
console.error('Failed to retrieve enrollment factors:', mfaError.error_description);
}
}
}
};
return { handleMfaFlow };
}
};
</script>All MFA operations are also available via the Options API using this.$auth0.mfa:
<script>
import { MfaRequiredError } from '@auth0/auth0-vue';
export default {
data() {
return {
mfaToken: null,
authenticators: []
};
},
methods: {
async fetchToken() {
try {
await this.$auth0.getAccessTokenSilently({
authorizationParams: { audience: 'https://api.example.com' }
});
} catch (e) {
if (e instanceof MfaRequiredError) {
this.mfaToken = e.mfa_token;
if (e.mfa_requirements?.enroll?.length) {
// User needs to enroll — show enrollment UI
} else {
// User is already enrolled — show challenge UI
this.authenticators = await this.$auth0.mfa.getAuthenticators(e.mfa_token);
}
}
}
},
async verifyOtp(otpCode) {
await this.$auth0.mfa.verify({
mfaToken: this.mfaToken,
otp: otpCode
});
// Refresh Vue reactive state (isAuthenticated, user, etc.)
await this.$auth0.checkSession();
}
}
};
</script>Step-Up Authentication is an alternative to building a custom MFA UI. When interactiveErrorHandler: 'popup' is configured, getAccessTokenSilently() automatically handles any mfa_required error by opening a Universal Login popup for the user to complete MFA — the token is then returned transparently with no extra code needed in your components.
import { createAuth0 } from '@auth0/auth0-vue';
const app = createApp(App);
app.use(
createAuth0({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: window.location.origin
},
useRefreshTokens: true,
interactiveErrorHandler: 'popup'
})
);With interactiveErrorHandler: 'popup' set, no special error handling is needed. If Auth0 requires MFA, the SDK opens Universal Login in a popup automatically:
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { getAccessTokenSilently } = useAuth0();
const fetchToken = async () => {
// If MFA is required, the SDK opens a popup automatically.
// The token is returned once the user completes authentication.
const token = await getAccessTokenSilently({
authorizationParams: { audience: 'https://api.example.com' }
});
console.log('Access token:', token);
};
return { fetchToken };
}
};
</script>If there is a problem with the popup, getAccessTokenSilently will throw one of PopupOpenError, PopupCancelledError, or PopupTimeoutError.
Exchange an external token for Auth0 tokens using the Custom Token Exchange grant (RFC 8693). This establishes a full Auth0 session — after a successful exchange, isAuthenticated will be true and user will be populated.
<script>
import { useAuth0 } from '@auth0/auth0-vue';
export default {
setup() {
const { loginWithCustomTokenExchange, isAuthenticated, user } = useAuth0();
const exchangeToken = async (externalToken) => {
try {
await loginWithCustomTokenExchange({
subject_token: externalToken,
subject_token_type: 'urn:your-company:legacy-system-token',
audience: 'https://api.example.com/',
scope: 'openid profile email'
});
} catch (e) {
console.error('Token exchange failed:', e);
}
};
return { exchangeToken, isAuthenticated, user };
}
};
</script>Using Options API
<script>
export default {
methods: {
async exchangeToken(externalToken) {
try {
await this.$auth0.loginWithCustomTokenExchange({
subject_token: externalToken,
subject_token_type: 'urn:your-company:legacy-system-token',
audience: 'https://api.example.com/',
scope: 'openid profile email'
});
} catch (e) {
console.error('Token exchange failed:', e);
}
}
}
};
</script>Notes:
subject_token_typemust be a namespaced URI under your organization's control. Well-known prefixes such asurn:ietf:params:oauth:*,urn:auth0:*, andhttps://auth0.com/*are reserved and should not be used for custom token types. See the Auth0 Custom Token Exchange documentation for details.- The external token must be validated in an Auth0 Action using strong cryptographic verification.
audienceandscopefall back to the SDK's configured defaults if not provided.