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 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. 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.
:::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
}),
```

View File

@@ -296,4 +296,16 @@ export interface AuthConfig {
cookies?: Partial<CookiesOptions> cookies?: Partial<CookiesOptions>
/** @todo */ /** @todo */
trustHost?: boolean 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 { url } = request
const warnings: WarningCode[] = [] const warnings: WarningCode[] = []
if (!warned && options.debug) warnings.push("debug_enabled") if (!warned && options.debug) warnings.push("debug-enabled")
if (!options.trustHost) { if (!options.trustHost) {
return new UntrustedHost(`Host must be trusted. URL was: ${request.url}`) 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 { UnknownAction } from "../errors.js"
import { skipCSRFCheck } from "../index.js"
import { SessionStore } from "./cookie.js"
import { init } from "./init.js" import { init } from "./init.js"
import renderPage from "./pages/index.js" import renderPage from "./pages/index.js"
import * as routes from "./routes/index.js" import * as routes from "./routes/index.js"
import type { import type {
RequestInternal,
ResponseInternal,
AuthConfig, AuthConfig,
ErrorPageParam, ErrorPageParam,
RequestInternal,
ResponseInternal,
} from "../types.js" } from "../types.js"
export async function AuthInternal< export async function AuthInternal<
@@ -19,6 +20,8 @@ export async function AuthInternal<
): Promise<ResponseInternal<Body>> { ): Promise<ResponseInternal<Body>> {
const { action, providerId, error, method } = request const { action, providerId, error, method } = request
const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck
const { options, cookies } = await init({ const { options, cookies } = await init({
authOptions, authOptions,
action, action,
@@ -28,6 +31,7 @@ export async function AuthInternal<
csrfToken: request.body?.csrfToken, csrfToken: request.body?.csrfToken,
cookies: request.cookies, cookies: request.cookies,
isPost: method === "POST", isPost: method === "POST",
csrfDisabled,
}) })
const sessionStore = new SessionStore( const sessionStore = new SessionStore(
@@ -48,12 +52,22 @@ export async function AuthInternal<
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...session, cookies } as any 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 { return {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: { csrfToken: options.csrfToken } as any, body: { csrfToken: options.csrfToken } as any,
cookies, cookies,
} }
}
case "signin": case "signin":
if (pages.signIn) { if (pages.signIn) {
let signinUrl = `${pages.signIn}${ let signinUrl = `${pages.signIn}${
@@ -125,8 +139,7 @@ export async function AuthInternal<
} else { } else {
switch (action) { switch (action) {
case "signin": case "signin":
// Verified CSRF Token required for all sign in routes if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
if (options.csrfTokenVerified && options.provider) {
const signin = await routes.signin( const signin = await routes.signin(
request.query, request.query,
request.body, request.body,
@@ -138,8 +151,7 @@ export async function AuthInternal<
return { redirect: `${options.url}/signin?csrf=true`, cookies } return { redirect: `${options.url}/signin?csrf=true`, cookies }
case "signout": case "signout":
// Verified CSRF Token required for signout if (csrfDisabled || options.csrfTokenVerified) {
if (options.csrfTokenVerified) {
const signout = await routes.signout(sessionStore, options) const signout = await routes.signout(sessionStore, options)
if (signout.cookies) cookies.push(...signout.cookies) if (signout.cookies) cookies.push(...signout.cookies)
return { ...signout, cookies } return { ...signout, cookies }
@@ -150,6 +162,7 @@ export async function AuthInternal<
// Verified CSRF Token required for credentials providers only // Verified CSRF Token required for credentials providers only
if ( if (
options.provider.type === "credentials" && options.provider.type === "credentials" &&
!csrfDisabled &&
!options.csrfTokenVerified !options.csrfTokenVerified
) { ) {
return { redirect: `${options.url}/signin?csrf=true`, cookies } 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 */ /** CSRF token value extracted from the incoming request. From body if POST, from query if GET */
csrfToken?: string csrfToken?: string
/** Is the incoming request a POST request? */ /** Is the incoming request a POST request? */
csrfDisabled: boolean
isPost: boolean isPost: boolean
cookies: RequestInternal["cookies"] cookies: RequestInternal["cookies"]
} }
@@ -38,6 +39,7 @@ export async function init({
cookies: reqCookies, cookies: reqCookies,
callbackUrl: reqCallbackUrl, callbackUrl: reqCallbackUrl,
csrfToken: reqCsrfToken, csrfToken: reqCsrfToken,
csrfDisabled,
isPost, isPost,
}: InitParams): Promise<{ }: InitParams): Promise<{
options: InternalOptions options: InternalOptions
@@ -117,6 +119,7 @@ export async function init({
const cookies: cookie.Cookie[] = [] const cookies: cookie.Cookie[] = []
if (!csrfDisabled) {
const { const {
csrfToken, csrfToken,
cookie: csrfCookie, cookie: csrfCookie,
@@ -138,6 +141,7 @@ export async function init({
options: options.cookies.csrfToken.options, options: options.cookies.csrfToken.options,
}) })
} }
}
const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({ const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({
options, options,

View File

@@ -1,6 +1,6 @@
import { AuthError } from "../../errors.js" 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. * Override any of the methods, and the rest will use the default logger.
@@ -38,7 +38,7 @@ export const logger: LoggerInstance = {
} }
}, },
warn(code) { 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}`) console.warn(`${yellow}[auth][warn][${code}]${reset}`, `Read more: ${url}`)
}, },
debug(message, metadata) { debug(message, metadata) {