forked from keycloak/keycloak
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement authentication flow mapping for authentication context clas…
…s reference (ACR) values. This implements an alternative mapping for the existing ACR feature where administrators can map requested ACR values to authentication flows, in addition to the existing level of authentication (LoA) mapping. It also implements a protocol mapper for populating the ACR claim in the resulting OIDC tokens. This implementation adds an ACR to auth flow mapping configuration at the client and realm level. When a client authenticates a user, and they request a particular ACR value, Keycloak will check for a mapped authentication flow in the client or realm configuration. If one is found, Keycloak will executed the corresponding authentication flow. If none match, then the existing LoA behavior will take place. Upon successful completion of an authentication flow, Keycloak tracks the flow ID in a user session note. The protocol mapper takes this completed authentication flow ID from the user session note and finds the associated ACR value from the realm or client configuration and sets it in the tokens. Closes keycloak#24297 Signed-off-by: Ben Cresitello-Dittmar <[email protected]>
- Loading branch information
Ben Cresitello-Dittmar
committed
Dec 20, 2023
1 parent
751cadc
commit 36f89d9
Showing
20 changed files
with
1,184 additions
and
26 deletions.
There are no files selected for viewing
Binary file added
BIN
+49 KB
docs/documentation/server_admin/images/client-oidc-map-acr-to-auth-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
docs/documentation/server_admin/topics/realms/acr-auth-flow-mapping.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[[_acr_auth_flow_mapping]] | ||
|
||
= ACR to Authentication Flow Mapping | ||
|
||
In the general settings of the realm, you can define mappings from `Authentication Context Class Reference (ACR)` values in OIDC authorization requests to authentication | ||
flows. This enables clients to request specific authentication flows using the OIDC ACR claim. For more details on using this claim see the | ||
https://openid.net/specs/openid-connect-core-1_0.html#acrSemantics[official OIDC specification]. | ||
|
||
ACR to authentication flow mappings and level of authentication (LoA) mappings provide two different ways for administrators to enable clients to request a particular strength of | ||
authentication when authenticating users. While LoA provides the ability to step-up authentication, sometimes it is necessary for applications to authenticate users with an | ||
entirely different policy in different scenarios. For these cases, clients can use the ACR to authentication flow mapping functionality. | ||
|
||
This mapping can be configured at both the realm level and the client level. If no mapping is configured at the client level, it will default to the realm level configuration. | ||
|
||
For more information about the client level configuration, see <<_mapping-acr-to-auth-flow-client,Client ACR to Authentication Flow Mapping>>. | ||
|
||
[NOTE] | ||
==== | ||
When both level of authentication (LoA) and auth flow mappings are defined for a given client or realm, Keycloak will first check if there is a valid auth flow | ||
mapping. If there is, then Keycloak will ignore any matching LoA mappings and route the authentication session to the mapped authentication flow. If there isn't, | ||
then the LoA process will continue as described in the chapter <<_mapping-acr-to-loa-client, ACR to Level of Authentication (LoA) Mapping>> | ||
==== | ||
|
||
image:images/client-oidc-map-acr-to-auth-flow.png[alt="ACR to auth flow mapping"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
215 changes: 215 additions & 0 deletions
215
js/apps/admin-ui/src/components/acr-flow-mapping/AcrFlowMapping.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
import { | ||
Button, | ||
FormGroup, | ||
Select, | ||
SelectOption, | ||
SelectVariant, | ||
Grid, | ||
GridItem, | ||
ActionList, | ||
ActionListItem, | ||
EmptyState, | ||
EmptyStateBody, | ||
HelperText, | ||
HelperTextItem, | ||
} from "@patternfly/react-core"; | ||
import { sortBy } from "lodash-es"; | ||
import { useState, Fragment } from "react"; | ||
import { Controller, useFormContext, useFieldArray } from "react-hook-form"; | ||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import { HelpItem } from "ui-shared"; | ||
|
||
import { adminClient } from "../../admin-client"; | ||
import { useFetch } from "../../utils/useFetch"; | ||
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput"; | ||
import { convertAttributeNameToForm } from "../../util"; | ||
|
||
export const AcrFlowMapping = () => { | ||
const { t } = useTranslation(); | ||
const [flows, setFlows] = useState<JSX.Element[]>([]); | ||
const [selectOpen] = useState({} as Record<string, boolean>); | ||
const [key, setKey] = useState(0); | ||
|
||
const name: string = convertAttributeNameToForm( | ||
"attributes.acr.authflow.map", | ||
); | ||
const { | ||
control, | ||
register, | ||
formState: { errors }, | ||
} = useFormContext(); | ||
|
||
const { fields, append, remove } = useFieldArray({ | ||
shouldUnregister: true, | ||
control, | ||
name, | ||
}); | ||
|
||
const appendNew = () => append({ key: "", value: "" }); | ||
|
||
const refresh = () => setKey(key + 1); | ||
|
||
const toggle = (k: string) => { | ||
const state = selectOpen[k] ? true : false; | ||
selectOpen[k] = !state; | ||
refresh(); | ||
}; | ||
|
||
useFetch( | ||
() => adminClient.authenticationManagement.getFlows(), | ||
(flows) => { | ||
let filteredFlows = [ | ||
...flows.filter((flow) => flow.providerId !== "client-flow"), | ||
]; | ||
filteredFlows = sortBy(filteredFlows, [(f) => f.alias]); | ||
setFlows([ | ||
<SelectOption key="empty" value=""> | ||
{t("choose")} | ||
</SelectOption>, | ||
...filteredFlows.map((flow) => ( | ||
<SelectOption key={flow.id} value={flow.id}> | ||
{flow.alias} | ||
</SelectOption> | ||
)), | ||
]); | ||
}, | ||
[], | ||
); | ||
|
||
return ( | ||
<FormGroup | ||
label={t("acrFlowMapping.title")} | ||
fieldId="acrFlowMapping" | ||
labelIcon={ | ||
<HelpItem | ||
helpText={t("acrFlowMapping.help")} | ||
fieldLabelId="acrFlowMapping" | ||
/> | ||
} | ||
> | ||
{fields.length > 0 ? ( | ||
<> | ||
<Grid hasGutter> | ||
<GridItem className="pf-c-form__label" span={5}> | ||
<span className="pf-c-form__label-text"> | ||
{t("acrFlowMapping.key.header")} | ||
</span> | ||
</GridItem> | ||
<GridItem className="pf-c-form__label" span={7}> | ||
<span className="pf-c-form__label-text"> | ||
{t("acrFlowMapping.value.header")} | ||
</span> | ||
</GridItem> | ||
{fields.map((attribute, index) => { | ||
const keyError = !!(errors as any).attributes?.[ | ||
name.replace("attributes.", "") | ||
]?.[index]?.key; | ||
const valueError = !!(errors as any).attributes?.[ | ||
name.replace("attributes.", "") | ||
]?.[index]?.value; | ||
|
||
return ( | ||
<Fragment key={attribute.id}> | ||
<GridItem span={5}> | ||
<KeycloakTextInput | ||
placeholder={t("acrFlowMapping.key.placeholder")} | ||
data-testid={`${name}-key`} | ||
{...register(`${name}.${index}.key`, { required: true })} | ||
validated={keyError ? "error" : "default"} | ||
isRequired | ||
/> | ||
{keyError && ( | ||
<HelperText> | ||
<HelperTextItem variant="error"> | ||
{t("acrFlowMapping.key.error")} | ||
</HelperTextItem> | ||
</HelperText> | ||
)} | ||
</GridItem> | ||
<GridItem span={5}> | ||
<Controller | ||
name={`${name}.${index}.value`} | ||
control={control} | ||
render={({ field }) => ( | ||
<Select | ||
placeholderText={t( | ||
"acrFlowMapping.value.placeholder", | ||
)} | ||
toggleId={`${name}.${index}.value`} | ||
variant={SelectVariant.single} | ||
onToggle={() => toggle(`${name}.${index}.value`)} | ||
isOpen={selectOpen[`${name}.${index}.value`]} | ||
onSelect={(_, value) => { | ||
field.onChange(value); | ||
toggle(`${name}.${index}.value`); | ||
}} | ||
selections={[field.value]} | ||
{...register(`${name}.${index}.value`, { | ||
required: true, | ||
})} | ||
validated={valueError ? "error" : "default"} | ||
> | ||
{flows} | ||
</Select> | ||
)} | ||
/> | ||
|
||
{valueError && ( | ||
<HelperText> | ||
<HelperTextItem variant="error"> | ||
{t("acrFlowMapping.value.error")} | ||
</HelperTextItem> | ||
</HelperText> | ||
)} | ||
</GridItem> | ||
<GridItem span={2}> | ||
<Button | ||
variant="link" | ||
title={t("removeAttribute")} | ||
onClick={() => remove(index)} | ||
data-testid={`${name}-remove`} | ||
> | ||
<MinusCircleIcon /> | ||
</Button> | ||
</GridItem> | ||
</Fragment> | ||
); | ||
})} | ||
</Grid> | ||
<ActionList> | ||
<ActionListItem> | ||
<Button | ||
data-testid={`${name}-add-row`} | ||
className="pf-u-px-0 pf-u-mt-sm" | ||
variant="link" | ||
icon={<PlusCircleIcon />} | ||
onClick={appendNew} | ||
> | ||
{t("acrFlowMapping.add")} | ||
</Button> | ||
</ActionListItem> | ||
</ActionList> | ||
</> | ||
) : ( | ||
<EmptyState | ||
data-testid={`${name}-empty-state`} | ||
className="pf-u-p-0" | ||
variant="xs" | ||
> | ||
<EmptyStateBody>{t("acrFlowMapping.empty")}</EmptyStateBody> | ||
<Button | ||
data-testid={`${name}-add-row`} | ||
variant="link" | ||
icon={<PlusCircleIcon />} | ||
isSmall | ||
onClick={appendNew} | ||
> | ||
{t("acrFlowMapping.add")} | ||
</Button> | ||
</EmptyState> | ||
)} | ||
</FormGroup> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.