Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: local with cookie session #564

Closed
wants to merge 4 commits into from
Closed
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
23 changes: 13 additions & 10 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import {
defineNuxtModule,
useLogger,
createResolver,
addTemplate,
addImports,
addPlugin,
addRouteMiddleware,
addServerPlugin,
addImports,
addRouteMiddleware
addTemplate,
createResolver,
defineNuxtModule,
useLogger
} from '@nuxt/kit'
import { defu } from 'defu'
import { joinURL } from 'ufo'
import { genInterface } from 'knitwork'
import type { DeepRequired } from 'ts-essentials'
import { joinURL } from 'ufo'
import { getOriginAndPathnameFromURL, isProduction } from './runtime/helpers'
import type {
AuthProviders,
ModuleOptions,
SupportedAuthProviders,
AuthProviders
SupportedAuthProviders
} from './runtime/types'

const topLevelDefaults = {
Expand Down Expand Up @@ -52,8 +52,11 @@
signInResponseTokenPointer: '/token',
type: 'Bearer',
headerName: 'Authorization',
sameSiteAttribute: 'lax',
name: 'auth:token',
maxAgeInSeconds: 30 * 60,
sameSiteAttribute: 'lax'
secure: false,
domain: undefined

Check failure on line 59 in src/module.ts

View workflow job for this annotation

GitHub Actions / test-module

Type 'undefined' is not assignable to type 'string'.
},
sessionDataType: { id: 'string | number' }
},
Expand All @@ -71,7 +74,7 @@
getSession: { path: '/session', method: 'get' },
refresh: { path: '/refresh', method: 'post' }
},
token: {

Check failure on line 77 in src/module.ts

View workflow job for this annotation

GitHub Actions / test-module

Type '{ signInResponseTokenPointer: string; type: string; headerName: string; maxAgeInSeconds: number; sameSiteAttribute: "none"; }' is missing the following properties from type '{ name: string; default: () => string | null; signInResponseTokenPointer: string; type: string; headerName: string; maxAgeInSeconds: number; sameSiteAttribute: boolean | "lax" | "strict" | "none"; secure: boolean; domain: string; }': name, default, secure, domain
signInResponseTokenPointer: '/token',
type: 'Bearer',
headerName: 'Authorization',
Expand Down
28 changes: 22 additions & 6 deletions src/runtime/composables/local/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { readonly, Ref } from 'vue'
import { callWithNuxt } from '#app/nuxt'
import { CommonUseAuthReturn, SignOutFunc, SignInFunc, GetSessionFunc, SecondarySignInOptions } from '../../types'
import { _fetch } from '../../utils/fetch'
import { jsonPointerGet, useTypedBackendConfig } from '../../helpers'
import { CommonUseAuthReturn, GetSessionFunc, SecondarySignInOptions, SignInFunc, SignOutFunc } from '../../types'
import { getRequestURLWN } from '../../utils/callWithNuxt'
import { _fetch } from '../../utils/fetch'
import { useAuthState } from './useAuthState'
import { callWithNuxt } from '#app/nuxt'
// @ts-expect-error - #auth not defined
import type { SessionData } from '#auth'
import { useNuxtApp, useRuntimeConfig, nextTick, navigateTo } from '#imports'
import { navigateTo, nextTick, useNuxtApp, useRuntimeConfig } from '#imports'

type Credentials = { username?: string, email?: string, password?: string } & Record<string, any>

Expand Down Expand Up @@ -43,13 +43,29 @@ const signIn: SignInFunc<Credentials, any> = async (credentials, signInOptions,
}
}

const addHeaders = (token: string | null, config: ReturnType<typeof useTypedBackendConfig>) => {
if (!(token && config.token.headerName)) {
return undefined
}

if (config.token.type.length > 0) {
switch (config.token.type) {
case 'Cookie':
return { [config.token.headerName]: `${config.token.name}=${token}` }
case 'Bearer':
default:
return { [config.token.headerName]: `${config.token.type} ${token}` }
}
}
}

const signOut: SignOutFunc = async (signOutOptions) => {
const nuxt = useNuxtApp()
const runtimeConfig = await callWithNuxt(nuxt, useRuntimeConfig)
const config = useTypedBackendConfig(runtimeConfig, 'local')
const { data, rawToken, token } = await callWithNuxt(nuxt, useAuthState)

const headers = new Headers({ [config.token.headerName]: token.value } as HeadersInit)
const headers = new Headers(addHeaders(token.value, config))
data.value = null
rawToken.value = null

Expand Down Expand Up @@ -80,7 +96,7 @@ const getSession: GetSessionFunc<SessionData | null | void> = async (getSessionO
return
}

const headers = new Headers(token.value ? { [config.token.headerName]: token.value } as HeadersInit : undefined)
const headers = new Headers(addHeaders(token.value, config))

loading.value = true
try {
Expand Down
13 changes: 7 additions & 6 deletions src/runtime/composables/local/useAuthState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { computed, watch, ComputedRef } from 'vue'
import type { CookieRef } from '#app'
import type { CookieRef } from '#app/nuxt'
import { ComputedRef, computed, watch } from 'vue'
import { useTypedBackendConfig } from '../../helpers'
import { CommonUseAuthStateReturn } from '../../types'
import { makeCommonAuthState } from '../commonAuthState'
import { useTypedBackendConfig } from '../../helpers'
import { useRuntimeConfig, useCookie, useState } from '#imports'
import { useCookie, useRuntimeConfig, useState } from '#imports'
// @ts-expect-error - #auth not defined
import type { SessionData } from '#auth'

Expand All @@ -19,7 +19,7 @@ export const useAuthState = (): UseAuthStateReturn => {
const commonAuthState = makeCommonAuthState<SessionData>()

// Re-construct state from cookie, also setup a cross-component sync via a useState hack, see https://github.com/nuxt/nuxt/issues/13020#issuecomment-1397282717
const _rawTokenCookie = useCookie<string | null>('auth:token', { default: () => null, maxAge: config.token.maxAgeInSeconds, sameSite: config.token.sameSiteAttribute })
const _rawTokenCookie = useCookie<string | null>(config.token.name, { default: config.token.default, maxAge: config.token.maxAgeInSeconds, sameSite: config.token.sameSiteAttribute, secure: config.token.secure, domain: config.token.domain })

const rawToken = useState('auth:raw-token', () => _rawTokenCookie.value)
watch(rawToken, () => { _rawTokenCookie.value = rawToken.value })
Expand All @@ -28,7 +28,8 @@ export const useAuthState = (): UseAuthStateReturn => {
if (rawToken.value === null) {
return null
}
return config.token.type.length > 0 ? `${config.token.type} ${rawToken.value}` : rawToken.value

return rawToken.value
})

const setToken = (newToken: string | null) => {
Expand Down
33 changes: 28 additions & 5 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Ref, ComputedRef } from 'vue'
import { RouterMethod } from 'h3'
import type { ComputedRef, Ref } from 'vue'
import { SupportedProviders } from './composables/authjs/useAuth'

/**
Expand Down Expand Up @@ -119,6 +119,14 @@ type ProviderLocal = {
* Settings for the authentication-token that `nuxt-auth` receives from the `signIn` endpoint and that can be used to authenticate subsequent requests.
*/
token?: {
/**
* The name of the cookie to store the authentication-token in.
*
* @default auth:token Access the cookie `auth:token` from session
*/
name?: string,

default?: () => string | null
/**
* How to extract the authentication-token from the sign-in response.
*
Expand All @@ -142,14 +150,14 @@ type ProviderLocal = {
* Header name to be used in requests that need to be authenticated, e.g., to be used in the `getSession` request.
*
* @default Authorization
* @example Auth
* @example Cookie
*/
headerName?: string;
/**
* Maximum age to store the authentication token for. After the expiry time the token is automatically deleted on the application side, i.e., in the users' browser.
*
* Note: Your backend may reject / expire the token earlier / differently.
* @default 1800
* @default undefined
* @example 60 * 60 * 24
*/
maxAgeInSeconds?: number;
Expand All @@ -159,8 +167,23 @@ type ProviderLocal = {
* @default 'lax'
* @example 'strict'
*/
sameSiteAttribute?: boolean | 'lax' | 'strict' | 'none' | undefined;
};
sameSiteAttribute?: boolean | 'lax' | 'strict' | 'none' | undefined,
/**
* Specifies the boolean value for the Secure Set-Cookie attribute. When truthy, the Secure attribute is set; otherwise it is not. By default, the Secure attribute is not set.
* Note: Be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie.
* @default false
* @example true
*/
secure?: boolean,
/**
* Specifies the value for the Domain Set-Cookie attribute. By default, no domain is set, and most clients will consider applying the cookie only to the current domain.
*
* @default undefined use
* @example 'domain.com'
*/
domain?: string,

},
/**
* Define an interface for the session data object that `nuxt-auth` expects to receive from the `getSession` endpoint.
*
Expand Down
Loading