Compare commits

...

3 Commits

Author SHA1 Message Date
Balázs Orbán
c9a47a5138 add warning if CSRF endpoint used when skipped 2023-01-12 15:43:50 +01:00
Balázs Orbán
da81e98084 fix logic 2023-01-12 15:24:27 +01:00
Balázs Orbán
56f414f8d6 feat(core): add way to opt-out of CSRF checks 2023-01-12 15:14:17 +01:00
6 changed files with 64 additions and 79 deletions

View File

@@ -3,57 +3,13 @@ id: warnings
title: Warnings
---
This is a list of warning output from Auth.js.
A list of warnings from Auth.js that need your attention.
All warnings indicate things which you should take a look at, but do not inhibit normal operation.
---
## Debug enabled
## Client
The `debug` option was evaluated to `true`. It adds extra logs in the terminal which is useful in development, but since it can print sensitive information about users, make sure to set this to `false` in production. In Node.js environments, you can for example set `debug: process.env.NODE_ENV !== "production"`. Consult with your runtime/framework on how to set this value correctly.
#### NEXTAUTH_URL
## CSRF disabled
Environment variable `NEXTAUTH_URL` missing. Please set it in your `.env` file.
:::note
On [Vercel](https://vercel.com) deployments, we will read the `VERCEL_URL` environment variable, so you won't need to define `NEXTAUTH_URL`.
:::
---
## Server
These warnings are displayed on the terminal.
#### NO_SECRET
In development, we generate a `secret` based on your configuration for convenience. This is volatile and will throw an error in production. [Read more](https://authjs.dev/reference/configuration/auth-config/#secret)
#### TWITTER_OAUTH_2_BETA
Twitter OAuth 2.0 is currently in beta as certain changes might still be necessary. This is not covered by semver. See the docs https://authjs.dev/reference/providers/twitter#oauth-2
#### EXPERIMENTAL_API
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
## Adapter
### ADAPTER_TYPEORM_UPDATING_ENTITIES
This warning occurs when typeorm finds that the provided entities differ from the database entities. By default while not in `production` the typeorm adapter will always synchronize changes made to the entities codefiles.
Disable this warning by setting `synchronize: false` in your typeorm config
Example:
```js title="/pages/api/auth/[...nextauth].js"
adapter: TypeORMLegacyAdapter({
type: 'mysql',
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_DB,
synchronize: false
}),
```
You were trying to get a CSRF response from Auth.js (eg.: by calling a `/csrf` endpoint), but in this setup, CSRF protection via Auth.js was turned off. This is likely if you are not directly using `@auth/core` but a framework library (like `@auth/sveltekit`) that already has CSRF protection built-in. You likely won't need the CSRF response.

View File

@@ -296,4 +296,16 @@ export interface AuthConfig {
cookies?: Partial<CookiesOptions>
/** @todo */
trustHost?: boolean
skipCSRFCheck?: typeof skipCSRFCheck
}
/**
* :::danger
* This option is inteded for framework authors.
* :::
*
* Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but
* if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by
* passing this value to {@link AuthConfig.skipCSRFCheck}.
*/
export const skipCSRFCheck = Symbol("skip-csrf-check")

View File

@@ -45,7 +45,7 @@ export function assertConfig(
const { url } = request
const warnings: WarningCode[] = []
if (!warned && options.debug) warnings.push("debug_enabled")
if (!warned && options.debug) warnings.push("debug-enabled")
if (!options.trustHost) {
return new UntrustedHost(`Host must be trusted. URL was: ${request.url}`)

View File

@@ -1,14 +1,15 @@
import { SessionStore } from "./cookie.js"
import { UnknownAction } from "../errors.js"
import { skipCSRFCheck } from "../index.js"
import { SessionStore } from "./cookie.js"
import { init } from "./init.js"
import renderPage from "./pages/index.js"
import * as routes from "./routes/index.js"
import type {
RequestInternal,
ResponseInternal,
AuthConfig,
ErrorPageParam,
RequestInternal,
ResponseInternal,
} from "../types.js"
export async function AuthInternal<
@@ -19,6 +20,8 @@ export async function AuthInternal<
): Promise<ResponseInternal<Body>> {
const { action, providerId, error, method } = request
const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck
const { options, cookies } = await init({
authOptions,
action,
@@ -28,6 +31,7 @@ export async function AuthInternal<
csrfToken: request.body?.csrfToken,
cookies: request.cookies,
isPost: method === "POST",
csrfDisabled,
})
const sessionStore = new SessionStore(
@@ -48,12 +52,22 @@ export async function AuthInternal<
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...session, cookies } as any
}
case "csrf":
case "csrf": {
if (csrfDisabled) {
options.logger.warn("csrf-disabled")
cookies.push({
name: options.cookies.csrfToken.name,
value: "",
options: { ...options.cookies.csrfToken.options, maxAge: 0 },
})
return { status: 404, cookies }
}
return {
headers: { "Content-Type": "application/json" },
body: { csrfToken: options.csrfToken } as any,
cookies,
}
}
case "signin":
if (pages.signIn) {
let signinUrl = `${pages.signIn}${
@@ -125,8 +139,7 @@ export async function AuthInternal<
} else {
switch (action) {
case "signin":
// Verified CSRF Token required for all sign in routes
if (options.csrfTokenVerified && options.provider) {
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
const signin = await routes.signin(
request.query,
request.body,
@@ -138,8 +151,7 @@ export async function AuthInternal<
return { redirect: `${options.url}/signin?csrf=true`, cookies }
case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
if (csrfDisabled || options.csrfTokenVerified) {
const signout = await routes.signout(sessionStore, options)
if (signout.cookies) cookies.push(...signout.cookies)
return { ...signout, cookies }
@@ -150,6 +162,7 @@ export async function AuthInternal<
// Verified CSRF Token required for credentials providers only
if (
options.provider.type === "credentials" &&
!csrfDisabled &&
!options.csrfTokenVerified
) {
return { redirect: `${options.url}/signin?csrf=true`, cookies }

View File

@@ -25,6 +25,7 @@ interface InitParams {
/** CSRF token value extracted from the incoming request. From body if POST, from query if GET */
csrfToken?: string
/** Is the incoming request a POST request? */
csrfDisabled: boolean
isPost: boolean
cookies: RequestInternal["cookies"]
}
@@ -38,6 +39,7 @@ export async function init({
cookies: reqCookies,
callbackUrl: reqCallbackUrl,
csrfToken: reqCsrfToken,
csrfDisabled,
isPost,
}: InitParams): Promise<{
options: InternalOptions
@@ -117,26 +119,28 @@ export async function init({
const cookies: cookie.Cookie[] = []
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
if (!csrfDisabled) {
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
})
}
}
const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({

View File

@@ -1,6 +1,6 @@
import { AuthError } from "../../errors.js"
export type WarningCode = "debug_enabled"
export type WarningCode = "debug-enabled" | "csrf-disabled"
/**
* Override any of the methods, and the rest will use the default logger.
@@ -38,7 +38,7 @@ export const logger: LoggerInstance = {
}
},
warn(code) {
const url = `https://errors.authjs.dev#${code}`
const url = `https://warnings.authjs.dev#${code}`
console.warn(`${yellow}[auth][warn][${code}]${reset}`, `Read more: ${url}`)
},
debug(message, metadata) {