Volverjs auth vue
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.From its SKILL.md
npx -y skills add volverjs/auth-vue --skill volverjs-auth-vueAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 11 stars11 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.3 KB, as published. Nobody here has run it
@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 # peer dependency: vue ^3.5
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.
- completes login if a
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/nonceare persisted tolocalStorageby default (scoped under anoauth.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', // OAuth issuer (must expose /.well-known/openid-configuration)
clientId: 'my-spa-client-id',
scopes: 'openid profile email offline_access', // offline_access → get a refresh token
redirectUri: `${location.origin}/callback`, // must be registered at the IdP
postLogoutRedirectUri: location.origin,
})
// Discovery + auto-complete login if returning from the IdP, before mount.
await auth.initialize()
app.use(auth, { global: true }) // { global: true } also exposes this.$vvAuth in Options API
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()
// import { auth } wherever you need it; loggedIn/accessToken are reactive.
The login round-trip
- User clicks Login →
auth.authorize()→ browser redirects to the IdP (with PKCEcode_challenge,state, andnoncewhenopenidis requested). - IdP redirects back to
redirectUriwith?code=...&state=.... - On that page,
auth.initialize()sees thecodeand completes the exchange automatically —loggedInflips totrueandaccessTokenis 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() // discovery
await auth.handleCodeResponse(new URLSearchParams(location.search))
// then router.replace('/') to drop the ?code=... from the URL
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 = oauthClient // your singleton or app-level client
if (to.meta.requiresAuth && !auth.loggedIn.value) {
auth.authorize() // redirects to the IdP
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). PreferstorageType: 'session'when you don't need persistent, cross-tab sessions, or pass a customstorage. - PKCE (
S256) and a randomstate(CSRF protection) are always used; anonce(OIDC replay protection) is added and validated only when theopenidscope 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 callinitialize()/authorize()on the client (e.g.onMounted, a client-only plugin, or guarded byimport.meta.client). - No refresh token? Add the
offline_accessscope (or the IdP's equivalent) — otherwiseinitialize()can't restore a session after reload and the user must log in again. openidrequires anid_token: when theopenidscope is set, the token response must include a (signed)id_tokenor the exchange fails. Standard IdPs do this; bare OAuth2 servers may not — dropopenidfor pure OAuth2.redirectUrimismatch 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 afterinitialize()completes the exchange;router.replace(location.pathname)to remove it. - Reactivity: in
<script>useauth.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.