diff --git a/.changeset/calm-tables-destroy.md b/.changeset/calm-tables-destroy.md new file mode 100644 index 0000000000000..3b9cc43571b54 --- /dev/null +++ b/.changeset/calm-tables-destroy.md @@ -0,0 +1,6 @@ +--- +"@directus/api": patch +"@directus/env": patch +--- + +Fixed potential Open Redirect vulnerability with OAuth2/OpenID/SAML SSO providers (via Directus redirect query parameter), by requiring redirect URLs to be enabled via allow list diff --git a/api/src/auth/drivers/oauth2.ts b/api/src/auth/drivers/oauth2.ts index d39d4356fb07b..8e8c1aac2c232 100644 --- a/api/src/auth/drivers/oauth2.ts +++ b/api/src/auth/drivers/oauth2.ts @@ -2,6 +2,7 @@ import { useEnv } from '@directus/env'; import { ErrorCode, InvalidCredentialsError, + InvalidPayloadError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, @@ -16,6 +17,7 @@ import jwt from 'jsonwebtoken'; import type { Client } from 'openid-client'; import { errors, generators, Issuer } from 'openid-client'; import { getAuthProvider } from '../../auth.js'; +import { REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../../constants.js'; import getDatabase from '../../database/index.js'; import emitter from '../../emitter.js'; import { useLogger } from '../../logger.js'; @@ -26,9 +28,9 @@ import type { AuthData, AuthDriverOptions, User } from '../../types/index.js'; import asyncHandler from '../../utils/async-handler.js'; import { getConfigFromEnv } from '../../utils/get-config-from-env.js'; import { getIPFromReq } from '../../utils/get-ip-from-req.js'; +import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js'; import { Url } from '../../utils/url.js'; import { LocalAuthDriver } from './local.js'; -import { REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../../constants.js'; export class OAuth2AuthDriver extends LocalAuthDriver { client: Client; @@ -298,15 +300,16 @@ export function createOAuth2AuthRouter(providerName: string): Router { const provider = getAuthProvider(providerName) as OAuth2AuthDriver; const codeVerifier = provider.generateCodeVerifier(); const prompt = !!req.query['prompt']; + const redirect = req.query['redirect']; - const token = jwt.sign( - { verifier: codeVerifier, redirect: req.query['redirect'], prompt }, - env['SECRET'] as string, - { - expiresIn: '5m', - issuer: 'directus', - }, - ); + if (isLoginRedirectAllowed(redirect, providerName) === false) { + throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` }); + } + + const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, env['SECRET'] as string, { + expiresIn: '5m', + issuer: 'directus', + }); res.cookie(`oauth2.${providerName}`, token, { httpOnly: true, diff --git a/api/src/auth/drivers/openid.ts b/api/src/auth/drivers/openid.ts index 732a2ad7af226..90166d7ad187d 100644 --- a/api/src/auth/drivers/openid.ts +++ b/api/src/auth/drivers/openid.ts @@ -2,6 +2,7 @@ import { useEnv } from '@directus/env'; import { ErrorCode, InvalidCredentialsError, + InvalidPayloadError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, @@ -16,6 +17,7 @@ import jwt from 'jsonwebtoken'; import type { Client } from 'openid-client'; import { errors, generators, Issuer } from 'openid-client'; import { getAuthProvider } from '../../auth.js'; +import { REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../../constants.js'; import getDatabase from '../../database/index.js'; import emitter from '../../emitter.js'; import { useLogger } from '../../logger.js'; @@ -26,9 +28,9 @@ import type { AuthData, AuthDriverOptions, User } from '../../types/index.js'; import asyncHandler from '../../utils/async-handler.js'; import { getConfigFromEnv } from '../../utils/get-config-from-env.js'; import { getIPFromReq } from '../../utils/get-ip-from-req.js'; +import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js'; import { Url } from '../../utils/url.js'; import { LocalAuthDriver } from './local.js'; -import { REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../../constants.js'; export class OpenIDAuthDriver extends LocalAuthDriver { client: Promise; @@ -320,7 +322,6 @@ const handleError = (e: any) => { export function createOpenIDAuthRouter(providerName: string): Router { const env = useEnv(); - const router = Router(); router.get( @@ -329,15 +330,16 @@ export function createOpenIDAuthRouter(providerName: string): Router { const provider = getAuthProvider(providerName) as OpenIDAuthDriver; const codeVerifier = provider.generateCodeVerifier(); const prompt = !!req.query['prompt']; + const redirect = req.query['redirect']; - const token = jwt.sign( - { verifier: codeVerifier, redirect: req.query['redirect'], prompt }, - env['SECRET'] as string, - { - expiresIn: '5m', - issuer: 'directus', - }, - ); + if (isLoginRedirectAllowed(redirect, providerName) === false) { + throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` }); + } + + const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, env['SECRET'] as string, { + expiresIn: '5m', + issuer: 'directus', + }); res.cookie(`openid.${providerName}`, token, { httpOnly: true, diff --git a/api/src/auth/drivers/saml.ts b/api/src/auth/drivers/saml.ts index 1b2cfad021b47..caf8a16aed551 100644 --- a/api/src/auth/drivers/saml.ts +++ b/api/src/auth/drivers/saml.ts @@ -1,6 +1,12 @@ import * as validator from '@authenio/samlify-node-xmllint'; import { useEnv } from '@directus/env'; -import { ErrorCode, InvalidCredentialsError, InvalidProviderError, isDirectusError } from '@directus/errors'; +import { + ErrorCode, + InvalidCredentialsError, + InvalidPayloadError, + InvalidProviderError, + isDirectusError, +} from '@directus/errors'; import express, { Router } from 'express'; import * as samlify from 'samlify'; import { getAuthProvider } from '../../auth.js'; @@ -15,6 +21,7 @@ import type { AuthDriverOptions, User } from '../../types/index.js'; import asyncHandler from '../../utils/async-handler.js'; import { getConfigFromEnv } from '../../utils/get-config-from-env.js'; import { LocalAuthDriver } from './local.js'; +import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js'; // Register the samlify schema validator samlify.setSchemaValidator(validator); @@ -126,7 +133,13 @@ export function createSAMLAuthRouter(providerName: string) { const parsedUrl = new URL(url); if (req.query['redirect']) { - parsedUrl.searchParams.append('RelayState', req.query['redirect'] as string); + const redirect = req.query['redirect'] as string; + + if (isLoginRedirectAllowed(redirect, providerName) === false) { + throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` }); + } + + parsedUrl.searchParams.append('RelayState', redirect); } return res.redirect(parsedUrl.toString()); diff --git a/api/src/utils/is-login-redirect-allowed.test.ts b/api/src/utils/is-login-redirect-allowed.test.ts new file mode 100644 index 0000000000000..b59b41f803468 --- /dev/null +++ b/api/src/utils/is-login-redirect-allowed.test.ts @@ -0,0 +1,78 @@ +import { vi, expect, test, afterEach } from 'vitest'; +import { useEnv } from '@directus/env'; +import { isLoginRedirectAllowed } from './is-login-redirect-allowed.js'; + +vi.mock('@directus/env'); + +afterEach(() => { + vi.clearAllMocks(); +}); + +test('isLoginRedirectAllowed returns true with no redirect', () => { + const redirect = undefined; + const provider = 'local'; + + expect(isLoginRedirectAllowed(redirect, provider)).toBe(true); +}); + +test('isLoginRedirectAllowed returns false with invalid redirect', () => { + const redirect = 123456; + const provider = 'local'; + + expect(isLoginRedirectAllowed(redirect, provider)).toBe(false); +}); + +test('isLoginRedirectAllowed returns true for allowed URL', () => { + const provider = 'local'; + + vi.mocked(useEnv).mockReturnValue({ + [`AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`]: + 'http://external.example.com,https://external.example.com,http://external.example.com:8055/test', + PUBLIC_URL: 'http://public.example.com', + }); + + expect(isLoginRedirectAllowed('http://public.example.com', provider)).toBe(true); + expect(isLoginRedirectAllowed('http://external.example.com', provider)).toBe(true); + expect(isLoginRedirectAllowed('https://external.example.com', provider)).toBe(true); + expect(isLoginRedirectAllowed('http://external.example.com:8055/test', provider)).toBe(true); +}); + +test('isLoginRedirectAllowed returns false for denied URL', () => { + const provider = 'local'; + + vi.mocked(useEnv).mockReturnValue({ + [`AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`]: 'http://external.example.com', + PUBLIC_URL: 'http://public.example.com', + }); + + expect(isLoginRedirectAllowed('https://external.example.com', provider)).toBe(false); + expect(isLoginRedirectAllowed('http://external.example.com:8055', provider)).toBe(false); + expect(isLoginRedirectAllowed('http://external.example.com/test', provider)).toBe(false); +}); + +test('isLoginRedirectAllowed returns true for relative paths', () => { + const provider = 'local'; + + vi.mocked(useEnv).mockReturnValue({ + [`AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`]: 'http://external.example.com', + PUBLIC_URL: 'http://public.example.com', + }); + + expect(isLoginRedirectAllowed('/admin/content', provider)).toBe(true); + expect(isLoginRedirectAllowed('../admin/content', provider)).toBe(true); + expect(isLoginRedirectAllowed('./admin/content', provider)).toBe(true); + + expect(isLoginRedirectAllowed('http://public.example.com/admin/content', provider)).toBe(true); +}); + +test('isLoginRedirectAllowed returns false if missing protocol', () => { + const provider = 'local'; + + vi.mocked(useEnv).mockReturnValue({ + [`AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`]: 'http://example.com', + PUBLIC_URL: 'http://example.com', + }); + + expect(isLoginRedirectAllowed('//example.com/admin/content', provider)).toBe(false); + expect(isLoginRedirectAllowed('//user@password:example.com/', provider)).toBe(false); +}); diff --git a/api/src/utils/is-login-redirect-allowed.ts b/api/src/utils/is-login-redirect-allowed.ts new file mode 100644 index 0000000000000..dd9b9f37d264f --- /dev/null +++ b/api/src/utils/is-login-redirect-allowed.ts @@ -0,0 +1,41 @@ +import { useEnv } from '@directus/env'; +import { toArray } from '@directus/utils'; +import isUrlAllowed from './is-url-allowed.js'; + +/** + * Checks if the defined redirect after successful SSO login is in the allow list + */ +export function isLoginRedirectAllowed(redirect: unknown, provider: string): boolean { + if (!redirect) return true; // empty redirect + if (typeof redirect !== 'string') return false; // invalid type + + const env = useEnv(); + const publicUrl = env['PUBLIC_URL'] as string; + + if (URL.canParse(redirect) === false) { + if (redirect.startsWith('//') === false) { + // should be a relative path like `/admin/test` + return true; + } + + // domain without protocol `//example.com/test` + return false; + } + + const { protocol: redirectProtocol, hostname: redirectDomain } = new URL(redirect); + + const envKey = `AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`; + + if (envKey in env) { + if (isUrlAllowed(redirect, [...toArray(env[envKey] as string), publicUrl])) return true; + } + + if (URL.canParse(publicUrl) === false) { + return false; + } + + // allow redirects to the defined PUBLIC_URL + const { protocol: publicProtocol, hostname: publicDomain } = new URL(publicUrl); + + return `${redirectProtocol}//${redirectDomain}` === `${publicProtocol}//${publicDomain}`; +} diff --git a/api/src/utils/is-url-allowed.test.ts b/api/src/utils/is-url-allowed.test.ts new file mode 100644 index 0000000000000..40def8ed349fc --- /dev/null +++ b/api/src/utils/is-url-allowed.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from 'vitest'; +import isUrlAllowed from './is-url-allowed.js'; + +test('isUrlAllowed should allow matching domain', () => { + const checkUrl = 'https://directus.io'; + const allowedUrls = ['https://directus.io/']; + + expect(isUrlAllowed(checkUrl, allowedUrls)).toBe(true); +}); + +test('isUrlAllowed should allow matching path', () => { + const checkUrl = 'https://directus.io/tv'; + const allowedUrls = ['https://directus.io/tv']; + + expect(isUrlAllowed(checkUrl, allowedUrls)).toBe(true); +}); + +test('isUrlAllowed should block different paths', () => { + const checkUrl = 'http://example.com/test1'; + const allowedUrls = ['http://example.com/test2', 'http://example.com/test3', 'http://example.com/']; + + expect(isUrlAllowed(checkUrl, allowedUrls)).toBe(false); +}); + +test('isUrlAllowed should block different domains', () => { + const checkUrl = 'http://directus.io/'; + const allowedUrls = ['http://example.com/', 'http://directus.chat']; + + expect(isUrlAllowed(checkUrl, allowedUrls)).toBe(false); +}); + +test('isUrlAllowed blocks varying protocols', () => { + const checkUrl = 'http://example.com/'; + const allowedUrls = ['ftp://example.com/', 'https://example.com/']; + + expect(isUrlAllowed(checkUrl, allowedUrls)).toBe(false); +}); diff --git a/api/src/utils/is-url-allowed.ts b/api/src/utils/is-url-allowed.ts index 3974e7e589f04..b5f8912836bca 100644 --- a/api/src/utils/is-url-allowed.ts +++ b/api/src/utils/is-url-allowed.ts @@ -3,7 +3,7 @@ import { URL } from 'url'; import { useLogger } from '../logger.js'; /** - * Check if url matches allow list either exactly or by domain+path + * Check if URL matches allow list either exactly or by origin (protocol+domain+port) + pathname */ export default function isUrlAllowed(url: string, allowList: string | string[]): boolean { const logger = useLogger(); @@ -15,8 +15,8 @@ export default function isUrlAllowed(url: string, allowList: string | string[]): const parsedWhitelist = urlAllowList .map((allowedURL) => { try { - const { hostname, pathname } = new URL(allowedURL); - return hostname + pathname; + const { origin, pathname } = new URL(allowedURL); + return origin + pathname; } catch { logger.warn(`Invalid URL used "${url}"`); } @@ -26,8 +26,8 @@ export default function isUrlAllowed(url: string, allowList: string | string[]): .filter((f) => f) as string[]; try { - const { hostname, pathname } = new URL(url); - return parsedWhitelist.includes(hostname + pathname); + const { origin, pathname } = new URL(url); + return parsedWhitelist.includes(origin + pathname); } catch { return false; } diff --git a/docs/releases/breaking-changes.md b/docs/releases/breaking-changes.md index 4d78a1514f12b..f7fd0cb90933f 100644 --- a/docs/releases/breaking-changes.md +++ b/docs/releases/breaking-changes.md @@ -134,6 +134,17 @@ AuthenticationService.login('email', 'password', { otp: 'otp-code', session: tru ::: +### Introduced Allow List for OAuth2/OpenID/SAML Redirects + +Due to an Open Redirect vulnerability with the OAuth2, OpenID and SAML SSO providers, we have introduced an allow list +for these redirects. + +If your current workflow depends on redirecting to an external domain after successful SSO login using the +`?redirect=http://example.com/login` query parameter, then you'll need to add this URL to the +`AUTH__REDIRECT_ALLOW_LIST` config option. + +`AUTH__REDIRECT_ALLOW_LIST` accepts a comma-separated list of URLs (path is included in comparison). + ## Version 10.9.0 ### Updated Exif Tags diff --git a/docs/self-hosted/config-options.md b/docs/self-hosted/config-options.md index 60378e48012c2..e32ba7dad03bd 100644 --- a/docs/self-hosted/config-options.md +++ b/docs/self-hosted/config-options.md @@ -747,23 +747,24 @@ These flows rely on the `PUBLIC_URL` variable for redirecting. Ensure the variab ### OAuth 2.0 -| Variable | Description | Default Value | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------- | -| `AUTH__CLIENT_ID` | Client identifier for the OAuth provider. | -- | -| `AUTH__CLIENT_SECRET` | Client secret for the OAuth provider. | -- | -| `AUTH__SCOPE` | A white-space separated list of permissions to request. | `email` | -| `AUTH__AUTHORIZE_URL` | Authorization page URL of the OAuth provider. | -- | -| `AUTH__ACCESS_URL` | Access token URL of the OAuth provider. | -- | -| `AUTH__PROFILE_URL` | User profile URL of the OAuth provider. | -- | -| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. Will default to `EMAIL_KEY`. | -- | -| `AUTH__EMAIL_KEY` | User profile email key. | `email` | -| `AUTH__FIRST_NAME_KEY` | User profile first name key. | -- | -| `AUTH__LAST_NAME_KEY` | User profile last name key. | -- | -| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH__ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | -| `AUTH__LABEL` | Text to be presented on SSO button within App. | `` | -| `AUTH__PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------- | +| `AUTH__CLIENT_ID` | Client identifier for the OAuth provider. | -- | +| `AUTH__CLIENT_SECRET` | Client secret for the OAuth provider. | -- | +| `AUTH__SCOPE` | A white-space separated list of permissions to request. | `email` | +| `AUTH__AUTHORIZE_URL` | Authorization page URL of the OAuth provider. | -- | +| `AUTH__ACCESS_URL` | Access token URL of the OAuth provider. | -- | +| `AUTH__PROFILE_URL` | User profile URL of the OAuth provider. | -- | +| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. Will default to `EMAIL_KEY`. | -- | +| `AUTH__EMAIL_KEY` | User profile email key. | `email` | +| `AUTH__FIRST_NAME_KEY` | User profile first name key. | -- | +| `AUTH__LAST_NAME_KEY` | User profile last name key. | -- | +| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH__ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | +| `AUTH__LABEL` | Text to be presented on SSO button within App. | `` | +| `AUTH__PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| `AUTH__REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | [1] When authenticating, Directus will match the identifier value from the external user profile to a Directus users "External Identifier". @@ -772,19 +773,20 @@ Directus users "External Identifier". OpenID is an authentication protocol built on OAuth 2.0, and should be preferred over standard OAuth 2.0 where possible. -| Variable | Description | Default Value | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------- | -| `AUTH__CLIENT_ID` | Client identifier for the external service. | -- | -| `AUTH__CLIENT_SECRET` | Client secret for the external service. | -- | -| `AUTH__SCOPE` | A white-space separated list of permissions to request. | `openid profile email` | -| `AUTH__ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- | -| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. Will default to `EMAIL_KEY`. | `sub`[2] | -| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH__REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` | -| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH__ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | -| `AUTH__LABEL` | Text to be presented on SSO button within App. | `` | -| `AUTH__PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------- | +| `AUTH__CLIENT_ID` | Client identifier for the external service. | -- | +| `AUTH__CLIENT_SECRET` | Client secret for the external service. | -- | +| `AUTH__SCOPE` | A white-space separated list of permissions to request. | `openid profile email` | +| `AUTH__ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- | +| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. Will default to `EMAIL_KEY`. | `sub`[2] | +| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH__REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` | +| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH__ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | +| `AUTH__LABEL` | Text to be presented on SSO button within App. | `` | +| `AUTH__PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| `AUTH__REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | [1] When authenticating, Directus will match the identifier value from the external user profile to a Directus users "External Identifier". @@ -848,16 +850,17 @@ without a password. - Identity provider (IdP) authenticates users and provides to service providers an authentication assertion that indicates a user has been authenticated. -| Variable | Description | Default Value | -| ------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | -| `AUTH__SP_metadata` | String containing XML metadata for service provider | -- | -| `AUTH__IDP_metadata` | String containing XML metadata for identity provider | -- | -| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | -| `AUTH__EMAIL_KEY` | User profile email key. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | -| `AUTH__GIVEN_NAME_KEY` | User first name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | -| `AUTH__FAMILY_NAME_KEY` | User last name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `AUTH__SP_metadata` | String containing XML metadata for service provider | -- | +| `AUTH__IDP_metadata` | String containing XML metadata for identity provider | -- | +| `AUTH__ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH__DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH__IDENTIFIER_KEY` | User profile identifier key [1]. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | +| `AUTH__EMAIL_KEY` | User profile email key. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | +| `AUTH__GIVEN_NAME_KEY` | User first name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | +| `AUTH__FAMILY_NAME_KEY` | User last name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | +| `AUTH__REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | [1] When authenticating, Directus will match the identifier value from the external user profile to a Directus users "External Identifier". diff --git a/packages/env/src/constants/directus-variables.ts b/packages/env/src/constants/directus-variables.ts index efaf3e95ab4b7..874cf9f214f97 100644 --- a/packages/env/src/constants/directus-variables.ts +++ b/packages/env/src/constants/directus-variables.ts @@ -153,6 +153,7 @@ export const DIRECTUS_VARIABLES = [ 'AUTH_.+_GROUP_SCOPE', 'AUTH_.+_IDP.+', 'AUTH_.+_SP.+', + 'AUTH_.+_REDIRECT_ALLOW_LIST', // extensions 'PACKAGE_FILE_LOCATION', 'EXTENSIONS_LOCATION',