| name | volverjs-auth-vue |
| description | Integrate OAuth2 / OpenID Connect authentication into a Vue 3 app with @volverjs/auth-vue. Use when adding login/logout, the authorization-code + PKCE flow, token refresh, an access token for API calls, route guards, or reactive auth state (loggedIn/accessToken) to a Vue 3 application. |
@volverjs/auth-vue is a thin, reactive Vue 3 wrapper around oauth4webapi for the OAuth 2.0 Authorization Code flow with PKCE (and OpenID Connect). It runs in the browser only and gives you reactive loggedIn / accessToken state, automatic token persistence, refresh, and logout.
Use this skill to wire it into a consuming Vue app. It does not require running this repo — it's reference for integrating the published package.
Install
pnpm add @volverjs/auth-vue
Mental model
- One client holds all config and reactive state. Create it once.
initialize() is the entry point you call on app start. After OAuth-server discovery it automatically:
- completes login if a
?code=... is present in the URL (returning from the IdP),
- otherwise refreshes the access token if a refresh token is stored,
- otherwise stays logged out.
authorize() sends the user to the IdP to log in. logout() sends them to the IdP to log out.
- The refresh token, code verifier and
state/nonce are persisted to localStorage by default (scoped under an oauth. key prefix).
Setup as a Vue plugin (recommended)
main.ts:
import { createOAuthClient } from '@volverjs/auth-vue'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
const auth = createOAuthClient({
url: 'https://my-idp.example.com',
clientId: 'my-spa-client-id',
scopes: 'openid profile email offline_access',
redirectUri: `${location.origin}/callback`,
postLogoutRedirectUri: location.origin,
})
await auth.initialize()
app.use(auth, { global: true })
app.mount('#app')
Then in any component get the client with the composable:
<script setup lang="ts">
import { useOAuthClient } from '@volverjs/auth-vue'
const auth = useOAuthClient() // must be called inside setup()
// auth.loggedIn → ComputedRef<boolean>
// auth.accessToken → readonly Ref<string | undefined>
</script>
<template>
<button v-if="!auth.loggedIn.value" @click="auth.authorize()">Login</button>
<button v-else @click="auth.logout()">Logout</button>
</template>
Setup as a standalone instance (no plugin)
import { OAuthClient } from '@volverjs/auth-vue'
export const auth = new OAuthClient({
url: 'https://my-idp.example.com',
clientId: 'my-spa-client-id',
scopes: ['openid', 'profile'],
})
await auth.initialize()
The login round-trip
- User clicks Login →
auth.authorize() → browser redirects to the IdP (with PKCE code_challenge, state, and nonce when openid is requested).
- IdP redirects back to
redirectUri with ?code=...&state=....
- On that page,
auth.initialize() sees the code and completes the exchange automatically — loggedIn flips to true and accessToken is populated.
If you handle the callback on a dedicated route and want to do it explicitly:
import { useOAuthClient } from '@volverjs/auth-vue'
const auth = useOAuthClient()
await auth.initialize()
await auth.handleCodeResponse(new URLSearchParams(location.search))
API reference
Core members used above: await initialize() (discovery + auto login/refresh), await authorize(), await handleCodeResponse(params), await refreshToken(), logout(hint?), plus reactive loggedIn / accessToken and the initialized flag. useOAuthClient(options?) returns the installed client inside setup().
For the full option table, method signatures and the confidential-client auth helpers, read references/api.md.
Common patterns
Attach the access token to API requests (reactive — re-reads the current token):
import { useOAuthClient } from '@volverjs/auth-vue'
export function useApi() {
const auth = useOAuthClient()
return (input: RequestInfo, init: RequestInit = {}) =>
fetch(input, {
...init,
headers: {
...init.headers,
...(auth.accessToken.value
? { Authorization: `Bearer ${auth.accessToken.value}` }
: {}),
},
})
}
Vue Router guard — require login, kicking off authorize() when needed:
router.beforeEach((to) => {
const auth =
if (to.meta.requiresAuth && !auth.loggedIn.value) {
auth.authorize()
return false
}
})
For a browser SPA, leave clientAuthentication unset (the default is a public client) and rely on PKCE — never ship a client secret to the browser. Confidential clients with a secret set clientAuthentication with a helper; see references/api.md.
Storage & security
- Default is
localStorage, which survives restarts and is readable by any JS on the origin (a single XSS exposes the refresh token). Prefer storageType: 'session' when you don't need persistent, cross-tab sessions, or pass a custom storage.
- PKCE (
S256) and a random state (CSRF protection) are always used; a nonce (OIDC replay protection) is added and validated only when the openid scope is requested.
- Keys are scoped under the storage base key (default
oauth.), so multiple instances don't collide.
Gotchas
- Browser-only (SSR/Nuxt): the client touches
document/window/Web Storage. Construction is SSR-safe, but call initialize()/authorize() on the client (e.g. onMounted, a client-only plugin, or guarded by import.meta.client).
- No refresh token? Add the
offline_access scope (or the IdP's equivalent) — otherwise initialize() can't restore a session after reload and the user must log in again.
openid requires an id_token: when the openid scope is set, the token response must include a (signed) id_token or the exchange fails. Standard IdPs do this; bare OAuth2 servers may not — drop openid for pure OAuth2.
redirectUri mismatch is the most common setup error: it must exactly match what's registered at the IdP.
- Clean the URL after login:
?code=...&state=... stays in the address bar after initialize() completes the exchange; router.replace(location.pathname) to remove it.
- Reactivity: in
<script> use auth.loggedIn.value / auth.accessToken.value; in <template> they auto-unwrap.
Migrating from 0.0.x
Upgrading an existing integration? The breaking change is tokenEndpointAuthMethod (a string) → clientAuthentication (a helper). Full mapping and other changes (oauth4webapi 3.x, Node ≥ 20, state/nonce) in references/migration.md.