Compare commits

..

9 Commits

Author SHA1 Message Date
GitHub Actions
9388a56efa chore(release): bump package version(s) [skip ci] 2023-01-04 06:56:11 +00:00
Balázs Orbán
3a75fb955a docs: simplify refresh token guide 2023-01-04 07:49:11 +01:00
Balázs Orbán
01bb91612a fix(core): allow passing params only to endpoint configs
fixes #6273
2023-01-04 07:48:57 +01:00
Balázs Orbán
3b25935c83 fix(core): correctly pass user id in account
fixes #6209, fixes #6222
2023-01-03 16:26:49 +01:00
Balázs Orbán
394920dfd4 chore(examples): prefer useSession over prop drilling
closes #5891
2023-01-03 16:07:37 +01:00
Raouf Chebri
85dc5bede8 docs: Update strava provider option url (#6264)
Update strava.md
2023-01-03 15:55:36 +01:00
Balázs Orbán
8f854c61d0 fix(core): allow custom endpoint config when issuer is present
closes #6244
2023-01-03 12:11:13 +01:00
Balázs Orbán
e8fbe58997 docs: update refresh token guide 2023-01-03 12:09:13 +01:00
Cameron Downey
d2288ee4cc fix(docs): turn SvelteKitAuth to named import in code example (#6250) 2023-01-02 10:52:47 +00:00
20 changed files with 278 additions and 293 deletions

View File

@@ -9,7 +9,6 @@ module.exports = {
files: [
"apps/dev/pages/api/auth/[...nextauth].ts",
"docs/{sidebars,docusaurus.config}.js",
"packages/core/src/index.ts",
],
options: { printWidth: 150 },
},

View File

@@ -0,0 +1,8 @@
// This is an example of how to access a session from an API route
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
res.json(session)
}

View File

@@ -1,21 +0,0 @@
import { Auth, SessionRequest } from "@auth/core"
import { authConfig } from "../auth/[...nextauth]"
export default async function handle(req: Request) {
authConfig.secret = process.env.AUTH_SECRET
const response = await Auth(new SessionRequest(req), authConfig)
const session = await response.session()
if (!session) {
return new Response("Not authenticated", { status: 401 })
}
console.log(session.user) // Do something with the session
// Pass the original headers to set cookies (eg.: updating the session expiry)
response.headers.set("content-type", "text/plain")
return new Response("Authenticated", { headers: response.headers })
}
export const config = {
runtime: "experimental-edge",
}

View File

@@ -3,9 +3,10 @@ import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
import type { GetServerSidePropsContext } from "next"
import type { Session } from "next-auth"
import { useSession } from "next-auth/react"
export default function ServerSidePage({ session }: { session: Session }) {
export default function ServerSidePage() {
const { data: session } = useSession()
// As this page uses Server Side Rendering, the `session` will be already
// populated on render without needing to go through a loading stage.
return (

View File

@@ -2,119 +2,203 @@
title: Refresh token rotation
---
While Auth.js doesn't automatically handle access token rotation for [OAuth providers](/reference/providers/oauth-builtin) yet, this functionality can be implemented using [callbacks](/guides/basics/callbacks).
Refresh token rotation is the practice of updating an `access_token` on behalf of the user, without requiring interaction (eg.: re-sign in). `access_token`s are usually issued for a limited time. After they expire, the service verifying them will ignore the value. Instead of asking the user to sign in again to obtain a new `access_token`, certain providers support exchanging a `refresh_token` for a new `access_token`, renewing the expiry time. Let's see how this can be achieved.
## Source Code
A working example can be accessed [here](https://github.com/nextauthjs/next-auth-refresh-token-example).
:::note
Our goal is to add zero-config support for built-in providers eventually. Let us know if you would like to help.
:::
## Implementation
First, make sure that the provider you want to use supports `refresh_token`'s. Check out [The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749#section-6) spec for more details.
### Server Side
Using a [JWT callback](https://authjs.dev/guides/basics/callbacks#jwt-callback) and a [session callback](https://authjs.dev/guides/basics/callbacks#session-callback), we can persist OAuth tokens and refresh them when they expire.
Depending on the session strategy, `refresh_token` can be persisted either in a database, or in a cookie, in an encrypted JWT.
:::info
Using a JWT to store the `refresh_token` is less secure than saving it in a database, and you need to evaluate based on your requirements which strategy you choose.
:::
#### JWT strategy
Using the [jwt](../../reference/03-core/interfaces/types.CallbacksOptions.md#jwt) and [session](../../reference/03-core/interfaces/types.CallbacksOptions.md#session) callbacks, we can persist OAuth tokens and refresh them when they expire.
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
```js title="pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
```ts
import { Auth } from "@auth/core"
import { type TokenSet } from "@auth/core/types"
import Google from "@auth/core/providers/google"
const GOOGLE_AUTHORIZATION_URL =
"https://accounts.google.com/o/oauth2/v2/auth?" +
new URLSearchParams({
prompt: "consent",
access_type: "offline",
response_type: "code",
})
/**
* Takes a token, and returns a new token with updated
* `accessToken` and `accessTokenExpires`. If an error occurs,
* returns the old token and an error property
*/
async function refreshAccessToken(token) {
try {
const url =
"https://oauth2.googleapis.com/token?" +
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
})
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
})
const refreshedTokens = await response.json()
if (!response.ok) {
throw refreshedTokens
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_at * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
}
} catch (error) {
console.log(error)
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
export default NextAuth({
export default Auth(new Request("https://example.com"), {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: GOOGLE_AUTHORIZATION_URL,
Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization: { params: { access_type: "offline", prompt: "consent" } },
}),
],
callbacks: {
async jwt({ token, user, account }) {
// Initial sign in
if (account && user) {
async jwt({ token, account }) {
if (account) {
// Save the access token and refresh token in the JWT on the initial login
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_at * 1000,
refreshToken: account.refresh_token,
user,
access_token: account.access_token,
expires_at: Date.now() + account.expires_in * 1000,
refresh_token: account.refresh_token,
}
} else if (Date.now() < token.expires_at) {
// If the access token has not expired yet, return it
return token
} else {
// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration
// We need the `token_endpoint`.
const response = await fetch("https://oauth2.googleapis.com/token", {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.GOOGLE_ID,
client_secret: process.env.GOOGLE_SECRET,
grant_type: "refresh_token",
refresh_token: token.refresh_token,
}),
method: "POST",
})
const tokens: TokenSet = await response.json()
if (!response.ok) throw tokens
return {
...token, // Keep the previous token properties
access_token: tokens.access_token,
expires_at: Date.now() + tokens.expires_in * 1000,
// Fall back to old refresh token, but note that
// many providers may only allow using a refresh token once.
refresh_token: tokens.refresh_token ?? token.refresh_token,
}
} catch (error) {
console.error("Error refreshing access token", error)
// The error property will be used client-side to handle the refresh token error
return { ...token, error: "RefreshAccessTokenError" as const }
}
}
// Return previous token if the access token has not expired yet
if (Date.now() < token.accessTokenExpires) {
return token
}
// Access token has expired, try to update it
return refreshAccessToken(token)
},
async session({ session, token }) {
session.user = token.user
session.accessToken = token.accessToken
session.error = token.error
return session
},
},
})
declare module "@auth/core/types" {
interface Session {
error?: "RefreshAccessTokenError"
}
}
declare module "@auth/core/jwt" {
interface JWT {
access_token: string
expires_at: number
refresh_token: string
error?: "RefreshAccessTokenError"
}
}
```
#### Database strategy
Using the database strategy is very similar, but instead of preserving the `access_token` and `refresh_token`, we save it, well, in the database.
```ts
import { Auth } from "@auth/core"
import { type TokenSet } from "@auth/core/types"
import Google from "@auth/core/providers/google"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export default Auth(new Request("https://example.com"), {
adapter: PrismaAdapter(prisma),
providers: [
Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization: { params: { access_type: "offline", prompt: "consent" } },
}),
],
callbacks: {
async session({ session, user }) {
const [google] = await prisma.account.findMany({
where: { userId: user.id, provider: "google" },
})
if (google.expires_at >= Date.now()) {
// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration
// We need the `token_endpoint`.
const response = await fetch("https://oauth2.googleapis.com/token", {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.GOOGLE_ID,
client_secret: process.env.GOOGLE_SECRET,
grant_type: "refresh_token",
refresh_token: google.refresh_token,
}),
method: "POST",
})
const tokens: TokenSet = await response.json()
if (!response.ok) throw tokens
await prisma.account.update({
data: {
access_token: tokens.access_token,
expires_at: Date.now() + tokens.expires_in * 1000,
refresh_token: tokens.refresh_token ?? google.refresh_token,
},
where: {
provider_providerAccountId: {
provider: "google",
providerAccountId: google.providerAccountId,
},
},
})
} catch (error) {
console.error("Error refreshing access token", error)
// The error property will be used client-side to handle the refresh token error
session.error = "RefreshAccessTokenError"
}
}
return session
},
},
})
declare module "@auth/core/types" {
interface Session {
error?: "RefreshAccessTokenError"
}
}
declare module "@auth/core/jwt" {
interface JWT {
access_token: string
expires_at: number
refresh_token: string
error?: "RefreshAccessTokenError"
}
}
```
### Client Side
The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed all the way to the client. This means that you can direct the user to the sign in flow if we cannot refresh their token.
The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed to the client. This means that you can direct the user to the sign-in flow if we cannot refresh their token.
We can handle this functionality as a side effect:
@@ -134,3 +218,8 @@ const HomePage() {
return (...)
}
```
## Source Code
A working example can be accessed [here](https://github.com/nextauthjs/next-auth-refresh-token-example).

View File

@@ -1,14 +1,14 @@
---
title: Available OAuth providers
sidebar_label: Oauth providers
sidebar_label: OAuth providers
---
Authentication Providers in **Auth.js** are services that can be used to sign in a user.
Authentication Providers in **Auth.js** are services that can be used to sign a user in.
Auth.js comes with a set of built-in providers. You can find them [here](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers). Each built-in provider has its own documentation page:
:::note
Auth.js is designed to work with any OAuth service, it supports **OAuth 1.0**, **1.0A**, **2.0** and **OpenID Connect (OIDC)** and has built-in support for most popular sign-in services.
Auth.js supports any **2.x** and **OpenID Connect (OIDC)** compliant providers and has built-in support for the most popular services.
:::
<ul>

View File

@@ -11,7 +11,7 @@ http://developers.strava.com/docs/reference/
The **Strava Provider** comes with a set of default options:
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.js)
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.ts)
You can override any of the options to suit your own use case.

View File

@@ -293,6 +293,6 @@ html[data-theme="dark"] #carbonads .carbon-poweredby {
See: https://github.com/TypeStrong/typedoc/issues/2006
*/
/* h3.anchor + p:has(code, strong), */ /** hack did not work as it hides property types elsewhere */
/* #classes {
#classes {
display: none;
} */
}

View File

@@ -1,7 +1,7 @@
{
"name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth",
"version": "1.0.5",
"version": "1.0.6",
"description": "AWS DynamoDB adapter for next-auth.",
"keywords": [
"next-auth",
@@ -44,4 +44,4 @@
"jest": "^27.4.3",
"next-auth": "workspace:*"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/core",
"version": "0.2.4",
"version": "0.2.5",
"description": "Authentication for the Web.",
"keywords": [
"authentication",

View File

@@ -40,17 +40,17 @@ import { logger, setLogger, type LoggerInstance } from "./lib/utils/logger.js"
import { toInternalRequest, toResponse } from "./lib/web.js"
import type { Adapter } from "./adapters.js"
import type { CallbacksOptions, CookiesOptions, EventCallbacks, PagesOptions, SessionOptions, Theme } from "./types.js"
import type {
CallbacksOptions,
CookiesOptions,
EventCallbacks,
PagesOptions,
SessionOptions,
Theme,
} from "./types.js"
import type { Provider } from "./providers/index.js"
import { JWTOptions } from "./jwt.js"
import { ProvidersRequest, ProvidersResponse, SessionRequest, SessionResponse } from "./lib/web-extension.js"
export * from "./lib/web-extension.js"
/** Returns a special {@link SessionResponse} instance to read the session from the request. */
export function Auth(request: SessionRequest, config: AuthConfig): Promise<SessionResponse>
/** Returns a special {@link ProvidersResponse} instance to read the list of providers in a client-safe way. */
export function Auth(request: ProvidersRequest, config: AuthConfig): Promise<ProvidersResponse>
/**
* Core functionality provided by Auth.js.
*
@@ -69,18 +69,19 @@ export function Auth(request: ProvidersRequest, config: AuthConfig): Promise<Pro
*```
* @see [Documentation](https://authjs.dev)
*/
export function Auth(request: Request, config: AuthConfig): Promise<Response>
export async function Auth(request: Request, config: AuthConfig): Promise<Response> {
export async function Auth(
request: Request,
config: AuthConfig
): Promise<Response> {
setLogger(config.logger, config.debug)
const isAuthRequest = request instanceof SessionRequest || request instanceof ProvidersRequest
if (isAuthRequest) config.trustHost = true
const internalRequest = await toInternalRequest(request)
if (internalRequest instanceof Error) {
logger.error(internalRequest)
return new Response(`Error: This action with HTTP ${request.method} is not supported.`, { status: 400 })
return new Response(
`Error: This action with HTTP ${request.method} is not supported.`,
{ status: 400 }
)
}
const assertionResult = assertConfig(internalRequest, config)
@@ -91,10 +92,14 @@ export async function Auth(request: Request, config: AuthConfig): Promise<Respon
// Bail out early if there's an error in the user config
logger.error(assertionResult)
const htmlPages = ["signin", "signout", "error", "verify-request"]
if (!htmlPages.includes(internalRequest.action) || internalRequest.method !== "GET") {
if (
!htmlPages.includes(internalRequest.action) ||
internalRequest.method !== "GET"
) {
return new Response(
JSON.stringify({
message: "There was a problem with the server configuration. Check the server logs for more information.",
message:
"There was a problem with the server configuration. Check the server logs for more information.",
code: assertionResult.name,
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
@@ -103,11 +108,19 @@ export async function Auth(request: Request, config: AuthConfig): Promise<Respon
const { pages, theme } = config
const authOnErrorPage = pages?.error && internalRequest.url.searchParams.get("callbackUrl")?.startsWith(pages.error)
const authOnErrorPage =
pages?.error &&
internalRequest.url.searchParams
.get("callbackUrl")
?.startsWith(pages.error)
if (!pages?.error || authOnErrorPage) {
if (authOnErrorPage) {
logger.error(new ErrorPageLoop(`The error page ${pages?.error} should not require authentication`))
logger.error(
new ErrorPageLoop(
`The error page ${pages?.error} should not require authentication`
)
)
}
const render = renderPage({ theme })
const page = render.error({ error: "Configuration" })
@@ -131,18 +144,6 @@ export async function Auth(request: Request, config: AuthConfig): Promise<Respon
headers: response.headers,
})
}
if (isAuthRequest) {
switch (request.action) {
case "session":
return new SessionResponse(response.body, response)
case "providers":
return new ProvidersResponse(response.body, response)
default:
return response
}
}
return response
}

View File

@@ -22,7 +22,8 @@ export async function getAuthorizationUrl(
let url = provider.authorization?.url
let as: o.AuthorizationServer | undefined
if (!url) {
// Falls back to authjs.dev if the user only passed params
if (!url || url.host === "authjs.dev") {
// If url is undefined, we assume that issuer is always defined
// We check this in assert.ts
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -48,9 +49,9 @@ export async function getAuthorizationUrl(
redirect_uri: provider.callbackUrl,
// @ts-expect-error TODO:
...provider.authorization?.params,
}, // Defaults
Object.fromEntries(authParams), // From provider config
query // From `signIn` call
},
Object.fromEntries(provider.authorization?.url.searchParams ?? []),
query
)
for (const k in params) authParams.set(k, params[k])

View File

@@ -31,7 +31,12 @@ export async function handleOAuth(
const { logger, provider } = options
let as: o.AuthorizationServer
if (!provider.token?.url && !provider.userinfo?.url) {
const { token, userinfo } = provider
// Falls back to authjs.dev if the user only passed params
if (
(!token?.url || token.url.host === "authjs.dev") &&
(!userinfo?.url || userinfo.url.host === "authjs.dev")
) {
// We assume that issuer is always defined as this has been asserted earlier
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const issuer = new URL(provider.issuer!)
@@ -54,9 +59,9 @@ export async function handleOAuth(
as = discoveredAs
} else {
as = {
issuer: provider.issuer ?? "https://a", // TODO: review fallback issuer
token_endpoint: provider.token?.url.toString(),
userinfo_endpoint: provider.userinfo?.url.toString(),
issuer: provider.issuer ?? "https://authjs.dev", // TODO: review fallback issuer
token_endpoint: token?.url.toString(),
userinfo_endpoint: userinfo?.url.toString(),
}
}
@@ -143,9 +148,9 @@ export async function handleOAuth(
throw new Error("TODO: Handle OAuth 2.0 response body error")
}
if (provider.userinfo?.request) {
profile = await provider.userinfo.request({ tokens, provider })
} else if (provider.userinfo?.url) {
if (userinfo?.request) {
profile = await userinfo.request({ tokens, provider })
} else if (userinfo?.url) {
const userinfoResponse = await o.userInfoRequest(
as,
client,

View File

@@ -45,11 +45,11 @@ export default function parseProviders(params: {
}
}
// TODO: Also add discovery here, if some endpoints/config are missing.
// We should return both a client and authorization server config.
function normalizeOAuth(
c?: OAuthConfig<any> | OAuthUserConfig<any>
c: OAuthConfig<any> | OAuthUserConfig<any>
): OAuthConfigInternal<any> | {} {
if (!c) return {}
if (c.issuer) c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration`
const authorization = normalizeEndpoint(c.authorization, c.issuer)
@@ -84,18 +84,18 @@ function normalizeEndpoint(
e?: OAuthConfig<any>[OAuthEndpointType],
issuer?: string
): OAuthConfigInternal<any>[OAuthEndpointType] {
if (!e || issuer) return
if (!e && issuer) return
if (typeof e === "string") {
return { url: new URL(e) }
}
// If v.url is undefined, it's because the provider config
// If e.url is undefined, it's because the provider config
// assumes that we will use the issuer endpoint.
// The existence of either v.url or provider.issuer is checked in
// assert.ts
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const url = new URL(e.url!)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
for (const k in e.params) url.searchParams.set(k, e.params[k] as any)
return { ...e, url }
// The existence of either e.url or provider.issuer is checked in
// assert.ts. We fallback to "https://authjs.dev" to be able to pass around
// a valid URL even if the user only provided params.
// NOTE: This need to be checked when constructing the URL
// for the authorization, token and userinfo endpoints.
const url = new URL(e?.url ?? "https://authjs.dev")
for (const k in e?.params) url.searchParams.set(k, e?.params[k])
return { url, request: e?.request }
}

View File

@@ -2,7 +2,7 @@ import { handleLogin } from "../callback-handler.js"
import { CallbackRouteError } from "../../errors.js"
import { handleOAuth } from "../oauth/callback.js"
import { createHash } from "../web.js"
import { handleAuthorized } from "./shared.js"
import { getAdapterUserFromEmail, handleAuthorized } from "./shared.js"
import type { AdapterSession } from "../../adapters.js"
import type {
@@ -10,6 +10,7 @@ import type {
ResponseInternal,
User,
InternalOptions,
Account,
} from "../../types.js"
import type { Cookie, SessionStore } from "../cookie.js"
@@ -172,40 +173,40 @@ export async function callback(params: {
}
// @ts-expect-error -- Verified in `assertConfig`.
const profile = await getAdapterUserFromEmail(identifier, adapter)
const user = await getAdapterUserFromEmail(identifier, adapter)
const account = {
providerAccountId: profile.email,
const account: Account = {
providerAccountId: user.email,
userId: user.id,
type: "email" as const,
provider: provider.id,
}
// Check if user is allowed to sign in
const unauthorizedOrError = await handleAuthorized(
{ user: profile, account },
{ user, account },
options
)
if (unauthorizedOrError) return { ...unauthorizedOrError, cookies }
// Sign user in
const { user, session, isNewUser } = await handleLogin(
sessionStore.value,
profile,
account,
options
)
const {
user: loggedInUser,
session,
isNewUser,
} = await handleLogin(sessionStore.value, user, account, options)
if (useJwtSession) {
const defaultToken = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString(),
name: loggedInUser.name,
email: loggedInUser.email,
picture: loggedInUser.image,
sub: loggedInUser.id?.toString(),
}
const token = await callbacks.jwt({
token: defaultToken,
user,
user: loggedInUser,
account,
isNewUser,
})
@@ -233,7 +234,7 @@ export async function callback(params: {
})
}
await events.signIn?.({ user, account, isNewUser })
await events.signIn?.({ user: loggedInUser, account, isNewUser })
// Handle first logins on new accounts
// e.g. option to send users to a new account landing page on initial login

View File

@@ -33,7 +33,7 @@ export async function signin(
const account: Account = {
providerAccountId: email,
userId: email,
userId: user.id,
type: "email",
provider: provider.id,
}

View File

@@ -1,95 +0,0 @@
import type { AuthAction, Session } from "../types.js"
import { PublicProvider } from "./routes/providers.js"
/** @internal */
export abstract class AuthRequest extends Request {
abstract action: AuthAction
}
/**
* Extends the standard {@link Request} to add a `session()` method on the response
* for retrieving the {@link Session} object.
*/
export class SessionRequest extends AuthRequest {
action = "session" as const
constructor(req: Request) {
super(req.url, req)
}
}
export class SessionResponse extends Response {
action = "session" as const
/**
* Returns the {@link Session} object from the response, or `null`
* if the session is unavailable (config error, not authenticated, etc.).
*
* @example
* ```ts
* export default async function handle(req: Request) {
* const response = await Auth(new SessionRequest(req), authConfig)
* const session = await response.session()
*
* if (!session) {
* return new Response("Not authenticated", { status: 401 })
* }
*
* console.log(session.user) // Do something with the session
* return response // or return whatever you want.
* }
* ```
*/
async session(): Promise<Session | null> {
try {
const data = await this.clone().json()
if (!this.ok || !data || !Object.keys(data).length) {
return null
}
return data
} catch {
return null
}
}
}
/**
* Extends the standard {@link Request} to add a `providers()` method on the response
* for retrieving a list of client-safe provider configuration. Useful for
* rendering a list of sign-in options.
*/
export class ProvidersRequest extends AuthRequest {
action = "providers" as const
constructor(req: Request) {
super(req.url, req)
}
}
export class ProvidersResponse extends Response {
action = "providers" as const
/**
* Returns the list of providers from the response, or `null`
* if the providers are unavailable (config error, etc.).
* @example
* ```ts
* export default async function handle(req: Request) {
* const response = await Auth(new ProvidersRequest(req), authConfig)
* const providers = await response.providers()
* if (!providers) {
* return new Response("Providers unavailable", { status: 500 })
*
*
* console.log(providers) // Do something with the providers
* return response // or return whatever you want.
* }
* ```
*/
async providers(): Promise<PublicProvider[]> {
try {
if (!this.ok) return []
return Object.values(await this.clone().json())
} catch {
return []
}
}
}

View File

@@ -2,7 +2,6 @@ import { parse as parseCookie, serialize } from "cookie"
import { AuthError, UnknownAction } from "../errors.js"
import type { AuthAction, RequestInternal, ResponseInternal } from "../types.js"
import { ProvidersRequest, SessionRequest } from "./web-extension.js"
async function getBody(req: Request): Promise<Record<string, any> | undefined> {
if (!("body" in req) || !req.body || req.method !== "POST") return
@@ -35,11 +34,8 @@ export async function toInternalRequest(
// see init.ts
const url = new URL(req.url.replace(/\/$/, ""))
const { pathname } = url
let action: AuthAction | undefined
if (req instanceof SessionRequest || req instanceof ProvidersRequest) {
action = req.action
} else action = actions.find((a) => pathname.includes(a))
const action = actions.find((a) => pathname.includes(a))
if (!action) {
throw new UnknownAction("Cannot detect action.")
}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/sveltekit",
"version": "0.1.11",
"version": "0.1.12",
"description": "Authentication for SvelteKit.",
"keywords": [
"authentication",

View File

@@ -18,7 +18,7 @@
* ## Usage
*
* ```ts title="src/hooks.server.ts"
* import SvelteKitAuth from "@auth/sveltekit"
* import { SvelteKitAuth } from "@auth/sveltekit"
* import GitHub from "@auth/core/providers/github"
* import { GITHUB_ID, GITHUB_SECRET } from "$env/static/private"
*