Skip to content

Commit

Permalink
Merge branch 'main' into refresh_authtoken_optional
Browse files Browse the repository at this point in the history
  • Loading branch information
zoey-kaiser authored Sep 18, 2024
2 parents f4447ba + 8c73bf3 commit f8ca293
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 59 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
2 changes: 1 addition & 1 deletion docs/.vitepress/routes/navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const routes: DefaultTheme.Config['nav'] = [
],
},
{
text: '0.9.1',
text: '0.9.2',
items: [
{
text: '0.8.2',
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/local/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export default defineNuxtConfig({
refreshOnlyToken: true,
token: {
signInResponseRefreshTokenPointer: '/refresh-token',
refreshRequestTokenPointer: 'Bearer',
refreshRequestTokenPointer: '/refresh-token',
cookieName: 'auth.token',
maxAgeInSeconds: 1800,
sameSiteAttribute: 'lax',
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
2 changes: 1 addition & 1 deletion docs/recipes/community/laravel-passport.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ export default NuxtAuthHandler({
```

:::tip Learn more
You can find the full discussion in the issue [#149](https://github.com/sidebase/nuxt-auth/v0.6/issues/149). Solution provided by [@Jericho1060](https://github.com/Jericho1060)
You can find the full discussion in the issue [#149](https://github.com/sidebase/nuxt-auth/issues/149). Solution provided by [@Jericho1060](https://github.com/Jericho1060)
:::
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 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 f8ca293

Please sign in to comment.