Protecting Routes
~8 minIn a Vue app, routes define the paths within the application that users can navigate to, linking URLs to specific components. Securing routes is essential to protect sensitive data, prevent unauthorized access, and ensure that only authenticated users can access certain parts of the application. The ThunderID SDK provides multiple approaches to secure routes in your application. This guide demonstrates how to secure routes in a Vue 3 single-page app using the navigation guard helper shipped with `@thunderid/vue`.
Protecting Routes
If you prefer full control over how app routes are secured, you can build a custom solution using the primitives provided by the ThunderID Vue SDK. This helps when you need to run custom application logic before enabling or disabling a route.
Basic Custom Route Guard With `<SignedIn />`
The simplest approach is to wrap the protected page content with the `` component and render a fallback for unauthenticated users:
<script setup>
</script>
<template>
<SignedIn>
<ContactDetails />
</SignedIn>
<SignedOut>
<p>You must sign in to view this page.</p>
</SignedOut>
</template>Custom Composable Using `useThunderID`
For more advanced control, such as role-based protection or a redirect, use `useThunderID()` together with Vue Router's `useRouter`:
export interface RequireAuthOptions {
redirectTo?: string
requireRole?: string
}
export function useRequireAuth(options: RequireAuthOptions = {}) {
const { redirectTo = '/signin', requireRole } = options
const { isInitialized, isSignedIn, user } = useThunderID()
const router = useRouter()
watchEffect(() => {
if (!isInitialized.value) return
if (!isSignedIn.value) {
router.replace(redirectTo)
return
}
if (requireRole && !user.value?.roles?.includes(requireRole)) {
router.replace('/forbidden')
}
})
}Custom Navigation Guard
You can also build a Vue Router navigation guard from scratch using the SDK's `inject` key directly. This is essentially what `createThunderIDGuard` does internally, and you can fork it to add custom logic.
export const requireAuth: NavigationGuard = async (to, from, next) => {
const ctx = inject<ThunderIDContext>(THUNDERID_KEY)
if (!ctx) {
return next({ path: '/signin' })
}
// Wait for initialization
while (!ctx.isInitialized.value) {
await new Promise((resolve) => requestAnimationFrame(resolve))
}
if (!ctx.isSignedIn.value) {
return next({ path: '/signin', query: { redirect: to.fullPath } })
}
// Add any custom role / permission checks here
return next()
}`beforeEnter` Route Guard
Apply the guard to a single route using `beforeEnter`. This is the most common pattern when only a few routes need protection.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/signin', component: SignIn },
{
path: '/dashboard',
component: Dashboard,
beforeEnter: createThunderIDGuard({ redirectTo: '/signin' }),
},
createCallbackRoute({ path: '/callback' }),
],
})
export default routerGlobal Guard with `router.beforeEach`
Apply the guard to every route at once using `router.beforeEach`. Combine this with per-route `meta` flags if you want to opt specific routes out of the check.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home, meta: { public: true } },
{ path: '/signin', component: SignIn, meta: { public: true } },
{ path: '/dashboard', component: Dashboard },
],
})
const requireAuth = createThunderIDGuard({ redirectTo: '/signin' })
router.beforeEach((to, from, next) => {
if (to.meta.public) {
return next()
}
return requireAuth(to, from, next)
})
export default routerCustomizing the Guard
The guard accepts the following options: | Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | `redirectTo` | `string` | `'/'` | Path to redirect unauthenticated users to | | `waitForInit` | `boolean` | `true` | Wait for the SDK to finish initializing before evaluating the auth state | | `initTimeout` | `number` | `10000` | Maximum time (in ms) to wait for SDK initialization before redirecting | For example, to reject immediately when the SDK is not yet initialized (without waiting):
const guard = createThunderIDGuard({
redirectTo: '/signin',
waitForInit: false,
})Using the Callback Route Helper
`createCallbackRoute` returns a route record that renders the SDK's built-in `` component, which extracts OAuth parameters from the URL and finalises the sign-in.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
createCallbackRoute({
path: '/auth/callback',
name: 'oauth-callback',
onError: (error) => console.error('OAuth error:', error),
}),
],
})