Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ jobs:
- name: πŸ“¦ Install dependencies
run: pnpm install

- name: 🎭 Install playwright
run: pnpm exec playwright install

- name: 🚧 Set up project
run: pnpm dev:prepare

Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default defineNuxtConfig({
// translations: {},
// fallbackLanguage: 'en'
// }
// globalMiddleware: false,
},
})
```
Expand All @@ -75,17 +76,39 @@ Check out the [Hanko documentation](https://docs.hanko.io/guides/vue) to learn m

### Middleware

By default two new route middleware are available in your Nuxt app: `hanko-logged-in` and `hanko-logged-out`.
By default two new route middlewares are available in your Nuxt app: `hanko-logged-in` and `hanko-logged-out`.

- `hanko-logged-in` will prevent access to a page unless you are logged in. (It will redirect you to `redirects.login` instead, and then redirect back to this page once you login. You can disable this behaviour by setting `redirects.followRedirect` to `false`.)
- `hanko-logged-out` will prevent access to a page unless you are logged out. (It will redirect you to `redirects.success` when you log in, and otherwise to `redirects.home`.)

You can also create your own middleware for full control.

### Global Middleware

If the `globalMiddleware` configuration is set to `true`, all of your pages are protected by default.
You can still override this behavior on each page, by applying a custom hanko pageMeta.
You can set a value for **either** allow or deny as shown below.

```TypeScript
definePageMeta({
hanko: {
// allow: 'all' | 'logged-in' | 'logged-out'
// deny: 'logged-in' | 'logged-out'
}
})
```

This pageMeta will only be taken into consideration when the `globalMiddleware` option is enabled.
You should not use hanko-middleware when `globalMiddleware` is enabled and instead use pageMeta.

**Note**: The `globalMiddleware` option will not apply any authentication checks to your API-paths.

### Auto-imports

`useHanko` is exposed in the Vue part of your app to allow you direct access to the Hanko API. You can access the current user and much more. **Note**: It will return `null` on the server.

The `hankoLoggedIn` and `hankoLoggedOut` middleware are exposed to enable you to extend their functionality, such as creating a custom global middleware.

### Server utilities

By default you can access a verified JWT context on `event.context.hanko`. (It will be undefined if the user is not logged in.) If you want to handle this yourself you can set `augmentContext: false`.
Expand Down
81 changes: 66 additions & 15 deletions playground/app.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,71 @@
<script setup lang="ts">
const nuxtConfig = useAppConfig()
const isGlobalMiddleware = computed(() => nuxtConfig.hanko.globalMiddleware)
const isTest = computed(() => process.env.VITEST === 'true')

const showNav = computed(() => isTest.value || !isGlobalMiddleware.value)
const showGlobalNav = computed(() => isTest.value || isGlobalMiddleware.value)
</script>

<template>
<nav>
<NuxtLink to="/">
Go home
</NuxtLink>
<NuxtLink to="/login">
Log in page β›”οΈπŸ‘€
</NuxtLink>
<NuxtLink to="/user">
User Page πŸ”
</NuxtLink>
<NuxtLink to="/protected">
Protected page πŸ”
</NuxtLink>
<NuxtLink to="/about">
About page
</NuxtLink>
<template v-if="showNav">
<NuxtLink to="/">
Go home
</NuxtLink>
<NuxtLink to="/login">
Log in page β›”οΈπŸ‘€
</NuxtLink>
<NuxtLink to="/user">
User Page πŸ”
</NuxtLink>
<NuxtLink to="/protected">
Protected page πŸ”
</NuxtLink>
<NuxtLink to="/about">
About page
</NuxtLink>
</template>

<!-- Global middleware -->
<template v-if="showGlobalNav">
<NuxtLink
id="allow-all"
to="/global/allow/all"
>
Allow all
</NuxtLink>
<NuxtLink
id="allow-logged-in"
to="/global/allow/logged-in"
>
Allow logged-in
</NuxtLink>
<NuxtLink
id="allow-logged-out"
to="/global/allow/logged-out"
>
Allow logged-out
</NuxtLink>
<NuxtLink
id="deny-logged-in"
to="/global/deny/logged-in"
>
Deny logged-in
</NuxtLink>
<NuxtLink
id="deny-logged-out"
to="/global/deny/logged-out"
>
Deny logged-out
</NuxtLink>
<NuxtLink
id="incorrect-usage"
to="/global/incorrect-usage"
>
Incorrect usage
</NuxtLink>
</template>
</nav>
<NuxtPage />
</template>
6 changes: 6 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export default defineNuxtConfig({
},
},
compatibilityDate: '2024-08-19',
// Expose VITEST to the client when running under Vitest so the nav shows both link groups in tests
vite: {
define: {
'process.env.VITEST': JSON.stringify(process.env.VITEST ?? ''),
},
},
hanko: {
// You need to provide the Hanko API URL in order for it to work
apiURL: '',
Expand Down
22 changes: 22 additions & 0 deletions playground/pages/global/allow/all.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
definePageMeta({
hanko: {
allow: 'all',
},
})
</script>

<template>
<main>
<h1>GlobalMiddleware: allow all</h1>
<div>
This page is accessible for both logged in and logged out users.
<pre>
definePageMeta({
hanko: {
allow: 'all',
}
})</pre>
</div>
</main>
</template>
18 changes: 18 additions & 0 deletions playground/pages/global/allow/logged-in.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
definePageMeta({ hanko: { allow: 'logged-in' } })
</script>

<template>
<main>
<h1>GlobalMiddleware: allow loggedIn</h1>
<div>
Only logged in users can see this page
<pre>
definePageMeta({
hanko: {
allow: 'logged-in',
}
})</pre>
</div>
</main>
</template>
18 changes: 18 additions & 0 deletions playground/pages/global/allow/logged-out.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
definePageMeta({ hanko: { allow: 'logged-out' } })
</script>

<template>
<main>
<h1>GlobalMiddleware: allow loggedOut</h1>
<div>
Only logged out users can see this page
<pre>
definePageMeta({
hanko: {
allow: 'logged-out',
}
})</pre>
</div>
</main>
</template>
22 changes: 22 additions & 0 deletions playground/pages/global/deny/logged-in.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
definePageMeta({
hanko: {
deny: 'logged-in',
},
})
</script>

<template>
<main>
<h1>GlobalMiddleware: deny loggedIn</h1>
<div>
This page is not accessible to loggedIn users
<pre>
definePageMeta({
hanko: {
deny: 'logged-in',
}
})</pre>
</div>
</main>
</template>
22 changes: 22 additions & 0 deletions playground/pages/global/deny/logged-out.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
definePageMeta({
hanko: {
deny: 'logged-out',
},
})
</script>

<template>
<main>
<h1>GlobalMiddleware: deny loggedOut</h1>
<div>
This page is not accessible to loggedOut users
<pre>
definePageMeta({
hanko: {
deny: 'logged-out',
}
})</pre>
</div>
</main>
</template>
13 changes: 13 additions & 0 deletions playground/pages/global/incorrect-usage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script setup lang="ts">
definePageMeta({
middleware: ['hanko-logged-in'],
hanko: { allow: 'all' },
})
</script>

<template>
<main>
<h1>Don't combine pageMeta:hanko and hanko-middleware</h1>
<p>In case you do, the middleware takes precedence.</p>
</main>
</template>
11 changes: 7 additions & 4 deletions playground/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ definePageMeta({
<template>
<main>
<h1>Log in</h1>
<p>
Only logged out users can see this page<pre>definePageMeta({
<div>
Only logged out users can see this page
<pre>
definePageMeta({
middleware: ['hanko-logged-out'],
})</pre>
</p>
</div>
<p v-if="$route.query.redirect">
You were redirected here from {{ $route.query.redirect }}, once you login, you'll be sent back automatically!
You were redirected here from {{ $route.query.redirect }}, once you login, you'll be sent back
automatically!
</p>
<hanko-auth />
</main>
Expand Down
8 changes: 5 additions & 3 deletions playground/pages/protected.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ function logout() {
<template>
<div>
<h1>Protected Page</h1>
<p>
Only logged in users can see this page<pre>definePageMeta({
<div>
Only logged in users can see this page
<pre>
definePageMeta({
middleware: ['hanko-logged-in'],
})</pre>
</p>
</div>
<button @click="logout">
Log me out
</button>
Expand Down
8 changes: 5 additions & 3 deletions playground/pages/user.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ async function tryAuthenticatedRequest() {
<template>
<main>
<h1>You are logged in!</h1>
<p>
Only logged in users can see this page<pre>definePageMeta({
<div>
Only logged in users can see this page
<pre>
definePageMeta({
middleware: ['hanko-logged-in'],
})</pre>
</p>
</div>
<button @click="logout">
Log me out
</button>
Expand Down
24 changes: 24 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ModuleOptions {
apiURL?: string
registerComponents?: boolean
augmentContext?: boolean
globalMiddleware?: boolean
cookieName?: string
cookieSameSite?: CookieSameSite
cookieDomain?: string
Expand Down Expand Up @@ -40,6 +41,7 @@ export default defineNuxtModule<ModuleOptions>({
apiURL: '',
registerComponents: true,
augmentContext: true,
globalMiddleware: false,
cookieName: 'hanko',
redirects: {
login: '/login',
Expand Down Expand Up @@ -68,6 +70,7 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.appConfig = defu(nuxt.options.appConfig, {
hanko: {
redirects: options.redirects,
globalMiddleware: options.globalMiddleware,
},
})

Expand All @@ -83,6 +86,19 @@ export default defineNuxtModule<ModuleOptions>({
})
}

if (options.globalMiddleware) {
addRouteMiddleware({
name: 'hanko-global-logged-in',
path: resolver.resolve('./runtime/middleware/global-logged-in'),
global: true,
})
nuxt.hook('prepare:types', ({ references }) => {
references.push({
path: resolver.resolve('./page-meta-global.d.ts'),
})
})
}

if (options.augmentContext) {
addServerHandler({
middleware: true,
Expand All @@ -107,6 +123,14 @@ export default defineNuxtModule<ModuleOptions>({
from: resolver.resolve('./runtime/composables/index'),
imports: ['useHanko'],
})
addImportsSources({
from: resolver.resolve('./runtime/middleware/logged-in'),
imports: ['hankoLoggedIn'],
})
addImportsSources({
from: resolver.resolve('./runtime/middleware/logged-out'),
imports: ['hankoLoggedOut'],
})

const hankoElementsTemplate = addTemplate({
filename: 'hanko-elements.mjs',
Expand Down
Loading