URL Redirection to Untrusted Site in OAuth2/OpenID in directus
Description
Directus is a real-time API and App dashboard for managing SQL database content. The authentication API has a redirect parameter that can be exploited as an open redirect vulnerability as the user tries to log in via the API URL. There's a redirect that is done after successful login via the Auth API GET request to directus/auth/login/google?redirect=http://malicious-fishing-site.com. While credentials don't seem to be passed to the attacker site, the user can be phished into clicking a legitimate directus site and be taken to a malicious site made to look like a an error message "Your password needs to be updated" to phish out the current password. Users who login via OAuth2 into Directus may be at risk. This issue has been addressed in version 10.10.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
directusnpm | < 10.10.0 | 10.10.0 |
Affected products
1Patches
15477d7d61babFix URL Redirection in OAuth2/OpenID/SAML (#21238)
11 files changed · +261 −66
api/src/auth/drivers/oauth2.ts+12 −9 modified@@ -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,
api/src/auth/drivers/openid.ts+12 −10 modified@@ -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<Client>; @@ -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,
api/src/auth/drivers/saml.ts+15 −2 modified@@ -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());
api/src/utils/is-login-redirect-allowed.test.ts+78 −0 added@@ -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); +});
api/src/utils/is-login-redirect-allowed.ts+41 −0 added@@ -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}`; +}
api/src/utils/is-url-allowed.test.ts+37 −0 added@@ -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); +});
api/src/utils/is-url-allowed.ts+5 −5 modified@@ -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; }
.changeset/calm-tables-destroy.md+6 −0 added@@ -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
docs/releases/breaking-changes.md+11 −0 modified@@ -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_<PROVIDER>_REDIRECT_ALLOW_LIST` config option. + +`AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` accepts a comma-separated list of URLs (path is included in comparison). + ## Version 10.9.0 ### Updated Exif Tags
docs/self-hosted/config-options.md+43 −40 modified@@ -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_<PROVIDER>_CLIENT_ID` | Client identifier for the OAuth provider. | -- | -| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the OAuth provider. | -- | -| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `email` | -| `AUTH_<PROVIDER>_AUTHORIZE_URL` | Authorization page URL of the OAuth provider. | -- | -| `AUTH_<PROVIDER>_ACCESS_URL` | Access token URL of the OAuth provider. | -- | -| `AUTH_<PROVIDER>_PROFILE_URL` | User profile URL of the OAuth provider. | -- | -| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. Will default to `EMAIL_KEY`. | -- | -| `AUTH_<PROVIDER>_EMAIL_KEY` | User profile email key. | `email` | -| `AUTH_<PROVIDER>_FIRST_NAME_KEY` | User profile first name key. | -- | -| `AUTH_<PROVIDER>_LAST_NAME_KEY` | User profile last name key. | -- | -| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | -| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` | -| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------- | +| `AUTH_<PROVIDER>_CLIENT_ID` | Client identifier for the OAuth provider. | -- | +| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the OAuth provider. | -- | +| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `email` | +| `AUTH_<PROVIDER>_AUTHORIZE_URL` | Authorization page URL of the OAuth provider. | -- | +| `AUTH_<PROVIDER>_ACCESS_URL` | Access token URL of the OAuth provider. | -- | +| `AUTH_<PROVIDER>_PROFILE_URL` | User profile URL of the OAuth provider. | -- | +| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. Will default to `EMAIL_KEY`. | -- | +| `AUTH_<PROVIDER>_EMAIL_KEY` | User profile email key. | `email` | +| `AUTH_<PROVIDER>_FIRST_NAME_KEY` | User profile first name key. | -- | +| `AUTH_<PROVIDER>_LAST_NAME_KEY` | User profile last name key. | -- | +| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | +| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` | +| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | <sup>[1]</sup> 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_<PROVIDER>_CLIENT_ID` | Client identifier for the external service. | -- | -| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the external service. | -- | -| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `openid profile email` | -| `AUTH_<PROVIDER>_ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- | -| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. Will default to `EMAIL_KEY`. | `sub`<sup>[2]</sup> | -| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH_<PROVIDER>_REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` | -| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | -| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` | -| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------- | +| `AUTH_<PROVIDER>_CLIENT_ID` | Client identifier for the external service. | -- | +| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the external service. | -- | +| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `openid profile email` | +| `AUTH_<PROVIDER>_ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- | +| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. Will default to `EMAIL_KEY`. | `sub`<sup>[2]</sup> | +| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH_<PROVIDER>_REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` | +| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` | +| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` | +| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- | +| `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | <sup>[1]</sup> 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_<PROVIDER>_SP_metadata` | String containing XML metadata for service provider | -- | -| `AUTH_<PROVIDER>_IDP_metadata` | String containing XML metadata for identity provider | -- | -| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | -| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | -| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | -| `AUTH_<PROVIDER>_EMAIL_KEY` | User profile email key. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | -| `AUTH_<PROVIDER>_GIVEN_NAME_KEY` | User first name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | -| `AUTH_<PROVIDER>_FAMILY_NAME_KEY` | User last name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | +| Variable | Description | Default Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `AUTH_<PROVIDER>_SP_metadata` | String containing XML metadata for service provider | -- | +| `AUTH_<PROVIDER>_IDP_metadata` | String containing XML metadata for identity provider | -- | +| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` | +| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- | +| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | +| `AUTH_<PROVIDER>_EMAIL_KEY` | User profile email key. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | +| `AUTH_<PROVIDER>_GIVEN_NAME_KEY` | User first name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | +| `AUTH_<PROVIDER>_FAMILY_NAME_KEY` | User last name attribute. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | +| `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- | <sup>[1]</sup> When authenticating, Directus will match the identifier value from the external user profile to a Directus users "External Identifier".
packages/env/src/constants/directus-variables.ts+1 −0 modified@@ -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',
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-fr3w-2p22-6w7pghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-28239ghsaADVISORY
- docs.directus.io/reference/authentication.htmlghsax_refsource_MISCWEB
- github.com/directus/directus/commit/5477d7d61babd7ffc2f835d399bf79611b15b203ghsax_refsource_MISCWEB
- github.com/directus/directus/security/advisories/GHSA-fr3w-2p22-6w7pghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.