mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
* oauth2/providers: add post logout redirect uri to providers * properly handle post_logout_redirect_uri and frontchannel message to rp * add backchannel support * move logout url logic * hanlde forbidden_uri_schemes on post_logout_redirect_uri * merge post_logout with redirect_uri --------- Signed-off-by: Connor Peshek <connor@connorpeshek.me> Co-authored-by: Jens L. <jens@goauthentik.io>
478 lines
20 KiB
TypeScript
478 lines
20 KiB
TypeScript
import "#components/ak-switch-input";
|
|
import "#admin/common/ak-crypto-certificate-search";
|
|
import "#admin/common/ak-flow-search/ak-flow-search";
|
|
import "#components/ak-hidden-text-input";
|
|
import "#components/ak-radio-input";
|
|
import "#components/ak-text-input";
|
|
import "#components/ak-textarea-input";
|
|
import "#elements/ak-array-input";
|
|
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
|
|
import "#elements/ak-dual-select/ak-dual-select-provider";
|
|
import "#elements/forms/FormGroup";
|
|
import "#elements/forms/HorizontalFormElement";
|
|
import "#elements/forms/Radio";
|
|
import "#elements/forms/SearchSelect/index";
|
|
import "#elements/utils/TimeDeltaHelp";
|
|
import "#admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
|
|
|
import { propertyMappingsProvider, propertyMappingsSelector } from "./OAuth2ProviderFormHelpers.js";
|
|
import { oauth2ProvidersProvider, oauth2ProvidersSelector } from "./OAuth2ProvidersProvider.js";
|
|
import { oauth2SourcesProvider, oauth2SourcesSelector } from "./OAuth2Sources.js";
|
|
|
|
import { ascii_letters, digits, randomString } from "#common/utils";
|
|
|
|
import { RadioOption } from "#elements/forms/Radio";
|
|
import { ifPresent } from "#elements/utils/attributes";
|
|
|
|
import { AKLabel } from "#components/ak-label";
|
|
|
|
import {
|
|
ClientTypeEnum,
|
|
FlowDesignationEnum,
|
|
IssuerModeEnum,
|
|
MatchingModeEnum,
|
|
OAuth2Provider,
|
|
OAuth2ProviderLogoutMethodEnum,
|
|
RedirectURI,
|
|
RedirectUriTypeEnum,
|
|
SubModeEnum,
|
|
ValidationError,
|
|
} from "@goauthentik/api";
|
|
|
|
import { msg } from "@lit/localize";
|
|
import { html } from "lit";
|
|
import { ifDefined } from "lit/directives/if-defined.js";
|
|
|
|
export const clientTypeOptions: RadioOption<ClientTypeEnum>[] = [
|
|
{
|
|
label: msg("Confidential"),
|
|
value: ClientTypeEnum.Confidential,
|
|
default: true,
|
|
description: html`${msg(
|
|
"Confidential clients are capable of maintaining the confidentiality of their credentials such as client secrets",
|
|
)}`,
|
|
},
|
|
{
|
|
label: msg("Public"),
|
|
value: ClientTypeEnum.Public,
|
|
description: html`${msg(
|
|
"Public clients are incapable of maintaining the confidentiality and should use methods like PKCE. ",
|
|
)}`,
|
|
},
|
|
];
|
|
|
|
export const logoutMethodOptions: RadioOption<OAuth2ProviderLogoutMethodEnum>[] = [
|
|
{
|
|
label: msg("Back-channel"),
|
|
value: OAuth2ProviderLogoutMethodEnum.Backchannel,
|
|
default: true,
|
|
description: html`${msg("Server-to-server logout notifications")}`,
|
|
},
|
|
{
|
|
label: msg("Front-channel"),
|
|
value: OAuth2ProviderLogoutMethodEnum.Frontchannel,
|
|
description: html`${msg("Browser iframe logout notifications")}`,
|
|
},
|
|
];
|
|
|
|
export const subjectModeOptions: RadioOption<SubModeEnum>[] = [
|
|
{
|
|
label: msg("Based on the User's hashed ID"),
|
|
value: SubModeEnum.HashedUserId,
|
|
default: true,
|
|
},
|
|
{
|
|
label: msg("Based on the User's ID"),
|
|
value: SubModeEnum.UserId,
|
|
},
|
|
{
|
|
label: msg("Based on the User's UUID"),
|
|
value: SubModeEnum.UserUuid,
|
|
},
|
|
{
|
|
label: msg("Based on the User's username"),
|
|
value: SubModeEnum.UserUsername,
|
|
},
|
|
{
|
|
label: msg("Based on the User's Email"),
|
|
value: SubModeEnum.UserEmail,
|
|
description: html`${msg("This is recommended over the UPN mode.")}`,
|
|
},
|
|
{
|
|
label: msg("Based on the User's UPN"),
|
|
value: SubModeEnum.UserUpn,
|
|
description: html`${msg(
|
|
"Requires the user to have a 'upn' attribute set, and falls back to hashed user ID. Use this mode only if you have different UPN and Mail domains.",
|
|
)}`,
|
|
},
|
|
];
|
|
|
|
export const issuerModeOptions: RadioOption<IssuerModeEnum>[] = [
|
|
{
|
|
label: msg("Each provider has a different issuer, based on the application slug"),
|
|
value: IssuerModeEnum.PerProvider,
|
|
default: true,
|
|
},
|
|
{
|
|
label: msg("Same identifier is used for all providers"),
|
|
value: IssuerModeEnum.Global,
|
|
},
|
|
];
|
|
|
|
const redirectUriHelpMessages: string[] = [
|
|
msg(
|
|
"Valid redirect URIs after a successful authorization or invalidation flow. Also specify any origins here for Implicit flows. Use the type dropdown to designate URIs for authorization or post-logout redirection.",
|
|
),
|
|
msg(
|
|
"If no explicit authorization redirect URIs are specified, the first successfully used authorization redirect URI will be saved.",
|
|
),
|
|
msg(
|
|
'To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.',
|
|
),
|
|
];
|
|
|
|
type ShowClientSecret = (show: boolean) => void;
|
|
type ShowLogoutMethod = (show: boolean) => void;
|
|
|
|
export interface OAuth2ProviderFormProps {
|
|
provider?: Partial<OAuth2Provider>;
|
|
errors?: ValidationError;
|
|
showClientSecret?: boolean;
|
|
showClientSecretCallback?: ShowClientSecret;
|
|
showLogoutMethod: boolean;
|
|
showLogoutMethodCallback: ShowLogoutMethod;
|
|
}
|
|
|
|
export function renderForm({
|
|
provider = {},
|
|
errors = {},
|
|
showClientSecret = false,
|
|
showClientSecretCallback = (_show) => undefined,
|
|
showLogoutMethod = false,
|
|
showLogoutMethodCallback = (_show) => undefined,
|
|
}: OAuth2ProviderFormProps) {
|
|
return html` <ak-text-input
|
|
name="name"
|
|
placeholder=${msg("Type a provider name...")}
|
|
autocomplete="off"
|
|
label=${msg("Provider Name")}
|
|
value=${ifDefined(provider.name)}
|
|
.errorMessages=${errors.name}
|
|
required
|
|
></ak-text-input>
|
|
|
|
<ak-form-element-horizontal name="authorizationFlow" required>
|
|
${AKLabel(
|
|
{
|
|
className: "pf-c-form__group-label",
|
|
slot: "label",
|
|
htmlFor: "authorizationFlow",
|
|
required: true,
|
|
},
|
|
msg("Authorization flow"),
|
|
)}
|
|
|
|
<ak-flow-search
|
|
id="authorizationFlow"
|
|
label=${msg("Authorization flow")}
|
|
placeholder=${msg("Select an authorization flow...")}
|
|
flowType=${FlowDesignationEnum.Authorization}
|
|
.currentFlow=${provider.authorizationFlow}
|
|
.errorMessages=${errors.authorizationFlow}
|
|
required
|
|
></ak-flow-search>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Flow used when authorizing this provider.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-group open label="${msg("Protocol settings")}">
|
|
<div class="pf-c-form">
|
|
<ak-radio-input
|
|
name="clientType"
|
|
label=${msg("Client type")}
|
|
.value=${provider.clientType}
|
|
required
|
|
@change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
|
|
showClientSecretCallback?.(ev.detail.value !== ClientTypeEnum.Public);
|
|
}}
|
|
.options=${clientTypeOptions}
|
|
>
|
|
</ak-radio-input>
|
|
<ak-text-input
|
|
name="clientId"
|
|
label=${msg("Client ID")}
|
|
value="${provider.clientId ?? randomString(40, ascii_letters + digits)}"
|
|
required
|
|
input-hint="code"
|
|
.errorMessages=${errors.clientId}
|
|
>
|
|
</ak-text-input>
|
|
<ak-hidden-text-input
|
|
name="clientSecret"
|
|
autocomplete="off"
|
|
label=${msg("Client Secret")}
|
|
value="${provider.clientSecret ?? randomString(128, ascii_letters + digits)}"
|
|
input-hint="code"
|
|
?hidden=${!showClientSecret}
|
|
>
|
|
</ak-hidden-text-input>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Redirect URIs/Origins (RegEx)")}
|
|
name="redirectUris"
|
|
>
|
|
<ak-array-input
|
|
.items=${provider.redirectUris ?? []}
|
|
.newItem=${() => ({
|
|
matchingMode: MatchingModeEnum.Strict,
|
|
url: "",
|
|
redirectUriType: RedirectUriTypeEnum.Authorization,
|
|
})}
|
|
.row=${(redirectURI: RedirectURI, idx: number) => {
|
|
return html`<ak-provider-oauth2-redirect-uri
|
|
.redirectURI=${redirectURI}
|
|
name="oauth2-redirect-uri"
|
|
style="width: 100%"
|
|
input-id="redirect-uri-${idx}"
|
|
></ak-provider-oauth2-redirect-uri>`;
|
|
}}
|
|
>
|
|
</ak-array-input>
|
|
${redirectUriHelpMessages.map(
|
|
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
|
)}
|
|
</ak-form-element-horizontal>
|
|
|
|
<ak-text-input
|
|
label=${msg("Logout URI")}
|
|
name="logoutUri"
|
|
value="${provider?.logoutUri ?? ""}"
|
|
input-hint="code"
|
|
inputmode="url"
|
|
placeholder=${msg("https://...")}
|
|
.help=${msg(
|
|
"URI to send logout notifications to when users log out. Required for OpenID Connect Logout functionality.",
|
|
)}
|
|
@input=${(ev: Event) => {
|
|
const target = ev.target as HTMLInputElement;
|
|
showLogoutMethodCallback?.(!!target.value);
|
|
}}
|
|
></ak-text-input>
|
|
|
|
${showLogoutMethod
|
|
? html`<ak-radio-input
|
|
label=${msg("Logout Method")}
|
|
name="logoutMethod"
|
|
.value=${provider.logoutMethod ||
|
|
OAuth2ProviderLogoutMethodEnum.Backchannel}
|
|
required
|
|
.options=${logoutMethodOptions}
|
|
.help=${msg(
|
|
"The logout method determines how the logout URI is called — back-channel (server-to-server) or front-channel (browser iframe).",
|
|
)}
|
|
></ak-radio-input>`
|
|
: html``}
|
|
|
|
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
|
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
|
<ak-crypto-certificate-search
|
|
label=${msg("Signing Key")}
|
|
placeholder=${msg("Select a signing key...")}
|
|
certificate=${ifPresent(provider.signingKey)}
|
|
singleton
|
|
></ak-crypto-certificate-search>
|
|
<p class="pf-c-form__helper-text">${msg("Key used to sign the tokens.")}</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>
|
|
|
|
<ak-form-group label=${msg("Advanced flow settings")}>
|
|
<div class="pf-c-form">
|
|
<ak-form-element-horizontal
|
|
name="authenticationFlow"
|
|
label=${msg("Authentication flow")}
|
|
>
|
|
<ak-flow-search
|
|
label=${msg("Authentication flow")}
|
|
placeholder=${msg("Select an authentication flow...")}
|
|
flowType=${FlowDesignationEnum.Authentication}
|
|
.currentFlow=${provider.authenticationFlow}
|
|
></ak-flow-search>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"Flow used when a user access this provider and is not authenticated.",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Invalidation flow")}
|
|
name="invalidationFlow"
|
|
required
|
|
>
|
|
<ak-flow-search
|
|
label=${msg("Invalidation flow")}
|
|
placeholder=${msg("Select an invalidation flow...")}
|
|
flowType=${FlowDesignationEnum.Invalidation}
|
|
.currentFlow=${provider.invalidationFlow}
|
|
defaultFlowSlug="default-provider-invalidation-flow"
|
|
required
|
|
></ak-flow-search>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Flow used when logging out of this provider.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>
|
|
|
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
|
<div class="pf-c-form">
|
|
<ak-text-input
|
|
name="accessCodeValidity"
|
|
label=${msg("Access code validity")}
|
|
input-hint="code"
|
|
required
|
|
value="${provider.accessCodeValidity ?? "minutes=1"}"
|
|
.bighelp=${html`<p class="pf-c-form__helper-text">
|
|
${msg("Configure how long access codes are valid for.")}
|
|
</p>
|
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
|
>
|
|
</ak-text-input>
|
|
<ak-text-input
|
|
name="accessTokenValidity"
|
|
label=${msg("Access Token validity")}
|
|
value="${provider.accessTokenValidity ?? "minutes=5"}"
|
|
input-hint="code"
|
|
required
|
|
.bighelp=${html` <p class="pf-c-form__helper-text">
|
|
${msg("Configure how long access tokens are valid for.")}
|
|
</p>
|
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
|
>
|
|
</ak-text-input>
|
|
|
|
<ak-text-input
|
|
name="refreshTokenValidity"
|
|
label=${msg("Refresh Token validity")}
|
|
value="${provider.refreshTokenValidity ?? "days=30"}"
|
|
input-hint="code"
|
|
required
|
|
.bighelp=${html` <p class="pf-c-form__helper-text">
|
|
${msg("Configure how long refresh tokens are valid for.")}
|
|
</p>
|
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
|
>
|
|
</ak-text-input>
|
|
<ak-text-input
|
|
name="refreshTokenThreshold"
|
|
label=${msg("Refresh Token threshold")}
|
|
value="${provider?.refreshTokenThreshold ?? "hours=1"}"
|
|
input-hint="code"
|
|
required
|
|
.bighelp=${html` <p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"When renewing a refresh token, if the existing refresh token's expiry is within this threshold, the refresh token will be renewed. Set to seconds=0 to always renew the refresh token.",
|
|
)}
|
|
</p>
|
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
|
>
|
|
</ak-text-input>
|
|
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
|
|
<ak-dual-select-dynamic-selected
|
|
.provider=${propertyMappingsProvider}
|
|
.selector=${propertyMappingsSelector(provider.propertyMappings)}
|
|
available-label=${msg("Available Scopes")}
|
|
selected-label=${msg("Selected Scopes")}
|
|
></ak-dual-select-dynamic-selected>
|
|
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal label=${msg("Encryption Key")} name="encryptionKey">
|
|
<!-- NOTE: 'null' cast to 'undefined' on encryptionKey to satisfy Lit requirements -->
|
|
<ak-crypto-certificate-search
|
|
label=${msg("Encryption Key")}
|
|
placeholder=${msg("Select an encryption key...")}
|
|
certificate=${ifPresent(provider.encryptionKey)}
|
|
></ak-crypto-certificate-search>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"Key used to encrypt the tokens. Only enable this if the application using this provider supports JWE tokens.",
|
|
)}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("authentik only supports RSA-OAEP-256 for encryption.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
|
|
<ak-radio-input
|
|
name="subMode"
|
|
label=${msg("Subject mode")}
|
|
required
|
|
.options=${subjectModeOptions}
|
|
.value=${provider.subMode}
|
|
help=${msg(
|
|
"Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
|
|
)}
|
|
>
|
|
</ak-radio-input>
|
|
<ak-switch-input
|
|
name="includeClaimsInIdToken"
|
|
label=${msg("Include claims in id_token")}
|
|
?checked=${provider.includeClaimsInIdToken ?? true}
|
|
help=${msg(
|
|
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
|
|
)}
|
|
></ak-switch-input>
|
|
<ak-radio-input
|
|
name="issuerMode"
|
|
label=${msg("Issuer mode")}
|
|
required
|
|
.options=${issuerModeOptions}
|
|
.value=${provider.issuerMode}
|
|
help=${msg("Configure how the issuer field of the ID Token should be filled.")}
|
|
>
|
|
</ak-radio-input>
|
|
</div>
|
|
</ak-form-group>
|
|
|
|
<ak-form-group label="${msg("Machine-to-Machine authentication settings")}">
|
|
<div class="pf-c-form">
|
|
<ak-form-element-horizontal
|
|
label=${msg("Federated OIDC Sources")}
|
|
name="jwtFederationSources"
|
|
>
|
|
<ak-dual-select-dynamic-selected
|
|
.provider=${oauth2SourcesProvider}
|
|
.selector=${oauth2SourcesSelector(provider.jwtFederationSources)}
|
|
available-label=${msg("Available Sources")}
|
|
selected-label=${msg("Selected Sources")}
|
|
></ak-dual-select-dynamic-selected>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Federated OAuth2/OpenID Providers")}
|
|
name="jwtFederationProviders"
|
|
>
|
|
<ak-dual-select-dynamic-selected
|
|
.provider=${oauth2ProvidersProvider}
|
|
.selector=${oauth2ProvidersSelector(provider.jwtFederationProviders)}
|
|
available-label=${msg("Available Providers")}
|
|
selected-label=${msg("Selected Providers")}
|
|
></ak-dual-select-dynamic-selected>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"JWTs signed by the selected providers can be used to authenticate to this provider.",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>`;
|
|
}
|