Skip to content

Commit

Permalink
Merge branch 'main' into 797/improve-base-url-detection
Browse files Browse the repository at this point in the history
  • Loading branch information
zoey-kaiser authored Sep 13, 2024
2 parents 9c3a555 + c32f9b1 commit 1d6b3f1
Show file tree
Hide file tree
Showing 16 changed files with 419 additions and 777 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
cache: 'pnpm'
cache: "pnpm"

- name: Install deps and prepare types
run: pnpm i && pnpm dev:prepare
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
cache: 'pnpm'
cache: "pnpm"

- name: Install deps and prepare types
run: pnpm i && pnpm dev:prepare
Expand All @@ -82,7 +82,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
cache: 'pnpm'
cache: "pnpm"

- name: Install deps
run: pnpm i
Expand All @@ -93,7 +93,12 @@ jobs:
# Check building
- run: pnpm build

- name: Run Playwright tests using Vitest
- name: Run Playwright tests using Vitest with refresh disabled
run: pnpm test:e2e
env:
NUXT_AUTH_REFRESH_ENABLED: false

- name: Run Playwright tests using Vitest with refresh enabled
run: pnpm test:e2e

test-playground-authjs:
Expand All @@ -113,7 +118,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
cache: 'pnpm'
cache: "pnpm"

- name: Install deps
run: pnpm i
Expand Down
22 changes: 21 additions & 1 deletion docs/guide/application-side/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,27 @@ Whether the module is enabled at all
- **Type**: `string`
- **Default**: `AUTH_ORIGIN`

The name of the environment variable that holds the origin of the application. This is used to determine the origin of your application in production. Read more [here](/resources/error-reference#auth-no-origin).
The name of the environment variable that holds the origin of the application. This is used to determine the origin of your application in production.

By default, NuxtAuth will look at `AUTH_ORIGIN` environment variable and `runtimeConfig.authOrigin`.

::: tip
If you want to use `runtimeConfig` and `NUXT_` prefixed environment variables, you need to make sure to also define the key inside `runtimeConfig`,
because otherwise Nuxt will not acknowledge your env variable ([issue #906](https://github.com/sidebase/nuxt-auth/issues/906), read more [here](https://nuxt.com/docs/guide/going-further/runtime-config#environment-variables)).

```ts
export default defineNuxtConfig({
auth: {
originEnvKey: 'NUXT_YOUR_ORIGIN'
},
runtimeConfig: {
yourOrigin: ''
}
})
```
:::

You can read additional information on `origin` determining [here](/resources/error-reference#auth-no-origin).

## `disableServerSideAuth`

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/authjs/custom-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ definePageMeta({
})
const route = useRoute()
const erorCode = computed(() => route.params.error)
const errorCode = computed(() => route.query.error)
</script>
<template>
Expand Down
17 changes: 17 additions & 0 deletions docs/guide/authjs/nuxt-auth-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ The providers are the registered authentication methods that your users can use

You can find an overview of all the prebuilt providers [here](https://next-auth.js.org/providers/). If you want to create your own provider, please visit the [NextAuth docs](https://next-auth.js.org/configuration/providers/oauth#using-a-custom-provider).

::: warning
`next-auth@4` providers require an additional `.default` to work in Vite. This will no longer be necessary in `next-auth@5` (`authjs`).

```ts
import GithubProvider from 'next-auth/providers/github'

export default NuxtAuthHandler({
providers: [
// @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
GithubProvider.default({ // [!code focus]
// GitHub provider configuration
})
]
})
```
:::

## Callbacks

The callbacks inside the NuxtAuthHandler are asynchronous functions that allow you to hook into and modify the authentication flow. This is helpful for when you need to:
Expand Down
24 changes: 14 additions & 10 deletions docs/guide/local/session-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export default defineNuxtConfig({
auth: {
provider: {
type: 'local',
sessionDataType: {
id: 'string | number',
firstName: 'string',
lastName: 'string'
}
session: {
dataType: {
id: 'string | number',
firstName: 'string',
lastName: 'string',
},
},
}
}
})
Expand All @@ -36,11 +38,13 @@ export default defineNuxtConfig({
auth: {
provider: {
type: 'local',
sessionDataType: {
id: 'string | number',
firstName: 'string',
lastName: 'string',
subscriptions: '{ id: number, active: boolean}[]'
session: {
dataType: {
id: 'string | number',
firstName: 'string',
lastName: 'string',
subscriptions: '{ id: number, active: boolean }[]'
},
}
}
}
Expand Down
34 changes: 25 additions & 9 deletions docs/resources/error-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,36 @@ export default NuxtAuthHandler({

## AUTH_NO_ORIGIN

`AUTH_NO_ORIGIN` will appear as a warning message during development and be thrown as an error that stops the application during production. It is safe to ignore the development warning - it is only meant as a heads-up for your later production-deployment. `AUTH_NO_ORIGIN` occurs when the origin of your application was not set. NuxtAuth tries to find the origin of your application in the following order:
`AUTH_NO_ORIGIN` will appear as a warning message during development and be thrown as an error that stops the application during production.
It is safe to ignore the development warning - it is only meant as a heads-up for your later production-deployment.

1. Use the `NUXT_AUTH_ORIGIN` environment variable if it is set
2. Development only: Determine the origin automatically from the incoming HTTP request
`AUTH_NO_ORIGIN` occurs when the origin of your application was not set.
NuxtAuth attempts to find the origin of your application in the following order ([source](https://github.com/sidebase/nuxt-auth/blob/9852116a7d3f3be56f6fdc1cba8bdff747c4cbb8/src/runtime/server/services/utils.ts#L8-L34)):

The `origin` is important for callbacks that happen to a specific origin for `oauth` flows. Note that in order for (2) to work the `origin` already has to be set at build-time, i.e., when you run `npm run build` or `npm run generate` and it will lead to the `origin` being inside your app-bundle.
### 1. Environment variable and `runtimeConfig`

Use the `AUTH_ORIGIN` environment variable or `runtimeConfig.authOrigin` if set. Name can be customized, refer to [`originEnvKey`](/guide/application-side/configuration#originenvkey).

### 2. `baseURL`

The `origin` is computed using `ufo` from the provided `baseURL`. See implementation [here](https://github.com/sidebase/nuxt-auth/blob/9852116a7d3f3be56f6fdc1cba8bdff747c4cbb8/src/runtime/helpers.ts#L9-L23).

```ts
// file: nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
authOrigin: 'https://example.org', // You can either set a default or leave it empty
auth: {
baseURL: `http://localhost:${process.env.PORT || 3000}`
}

// ... rest of your config
})
```

### 3. Development only: automatically from the incoming HTTP request

When the server is running in development mode, NuxtAuth can automatically infer it from the incoming request.

::: info
This is only done for your convenience - make sure to set a proper origin in production.
:::

---

If there is no valid `origin` after the steps above, `AUTH_NO_ORIGIN` error is thrown in production.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sidebase/nuxt-auth",
"version": "0.9.1",
"version": "0.9.2",
"license": "MIT",
"type": "module",
"description": "Authentication built for Nuxt 3! Easily add authentication via OAuth providers, credentials or Email Magic URLs!",
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"devDependencies": {
"@antfu/eslint-config": "^2.25.0",
"@nuxt/module-builder": "^0.5.5",
"@nuxt/module-builder": "^0.8.3",
"@nuxt/schema": "^3.12.4",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@types/node": "^18.19.42",
Expand Down
4 changes: 2 additions & 2 deletions playground-local/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { useAuth } from '#imports'
const { signIn, token, refreshToken, data, status, lastRefreshedAt, signOut, getSession } = useAuth()
const username = ref('')
const password = ref('')
const username = ref('smith')
const password = ref('hunter2')
</script>

<template>
Expand Down
17 changes: 17 additions & 0 deletions playground-local/config/AuthRefreshHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { RefreshHandler } from '../../'

// You may also use a plain object with `satisfies RefreshHandler`, of course!
class CustomRefreshHandler implements RefreshHandler {
init(): void {
console.info('Use the full power of classes to customize refreshHandler!')
}

destroy(): void {
console.info(
'Hover above class properties or go to their definition '
+ 'to learn more about how to craft a refreshHandler'
)
}
}

export default new CustomRefreshHandler()
14 changes: 13 additions & 1 deletion playground-local/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,25 @@ export default defineNuxtConfig({
session: {
dataType: { id: 'string', email: 'string', name: 'string', role: '\'admin\' | \'guest\' | \'account\'', subscriptions: '{ id: number, status: \'ACTIVE\' | \'INACTIVE\' }[]' },
dataResponsePointer: '/'
},
refresh: {
// This is usually a static configuration `true` or `false`.
// We do an environment variable for E2E testing both options.
isEnabled: process.env.NUXT_AUTH_REFRESH_ENABLED !== 'false',
endpoint: { path: '/refresh', method: 'post' },
token: {
signInResponseRefreshTokenPointer: '/token/refreshToken',
refreshRequestTokenPointer: '/refreshToken'
},
}
},
sessionRefresh: {
// Whether to refresh the session every time the browser window is refocused.
enableOnWindowFocus: true,
// Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists.
enablePeriodically: 5000
enablePeriodically: 5000,
// Custom refresh handler - uncomment to use
// handler: './config/AuthRefreshHandler'
},
globalAppMiddleware: {
isEnabled: true
Expand Down
80 changes: 71 additions & 9 deletions playground-local/server/api/auth/login.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,85 @@ import { createError, eventHandler, readBody } from 'h3'
import { z } from 'zod'
import { sign } from 'jsonwebtoken'

const refreshTokens: Record<number, Record<string, any>> = {}
/*
* DISCLAIMER!
* This is a demo implementation, please create your own handlers
*/

/**
* This is a demo secret.
* Please ensure that your secret is properly protected.
*/
export const SECRET = 'dummy'

/** 30 seconds */
export const ACCESS_TOKEN_TTL = 30

export interface User {
username: string
name: string
picture: string
}

export interface JwtPayload extends User {
scope: Array<'test' | 'user'>
exp?: number
}

interface TokensByUser {
access: Map<string, string>
refresh: Map<string, string>
}

/**
* Tokens storage.
* You will need to implement your own, connect with DB/etc.
*/
export const tokensByUser: Map<string, TokensByUser> = new Map()

/**
* We use a fixed password for demo purposes.
* You can use any implementation fitting your usecase.
*/
const credentialsSchema = z.object({
username: z.string().min(1),
password: z.literal('hunter2')
})

export default eventHandler(async (event) => {
const result = z.object({ username: z.string().min(1), password: z.literal('hunter2') }).safeParse(await readBody(event))
const result = credentialsSchema.safeParse(await readBody(event))
if (!result.success) {
throw createError({ statusCode: 403, statusMessage: 'Unauthorized, hint: try `hunter2` as password' })
throw createError({
statusCode: 403,
statusMessage: 'Unauthorized, hint: try `hunter2` as password'
})
}

const expiresIn = 15
const refreshToken = Math.floor(Math.random() * (1000000000000000 - 1 + 1)) + 1
// Emulate login
const { username } = result.data
const user = {
username,
picture: 'https://github.com/nuxt.png',
name: `User ${username}`
}

const accessToken = sign({ ...user, scope: ['test', 'user'] }, SECRET, { expiresIn })
refreshTokens[refreshToken] = {
accessToken,
user
const tokenData: JwtPayload = { ...user, scope: ['test', 'user'] }
const accessToken = sign(tokenData, SECRET, {
expiresIn: ACCESS_TOKEN_TTL
})
const refreshToken = sign(tokenData, SECRET, {
// 1 day
expiresIn: 60 * 60 * 24
})

// Naive implementation - please implement properly yourself!
const userTokens: TokensByUser = tokensByUser.get(username) ?? {
access: new Map(),
refresh: new Map()
}
userTokens.access.set(accessToken, refreshToken)
userTokens.refresh.set(refreshToken, accessToken)
tokensByUser.set(username, userTokens)

return {
token: {
Expand All @@ -33,3 +89,9 @@ export default eventHandler(async (event) => {
}
}
})

export function extractToken(authorizationHeader: string) {
return authorizationHeader.startsWith('Bearer ')
? authorizationHeader.slice(7)
: authorizationHeader
}
Loading

0 comments on commit 1d6b3f1

Please sign in to comment.