Chapter 6. Authentication
Chapter 6. Authentication There are a few features you should be aware of when configuring authentication for your realm. Many organizations have strict password and OTP policies that you can enforce via settings in the Admin Console. You may or may not want to require different credential types for authentication. You may want to give users the option to login via Kerberos or disable or enable various built-in credential types. This chapter covers all of these topics. 6.1. Password Policies Each new realm created has no password policies associated with it. Users can have as short, as long, as complex, as insecure a password, as they want. Simple settings are fine for development or learning Red Hat Single Sign-On, but unacceptable in production environments. Red Hat Single Sign-On has a rich set of password policies you can enable through the Admin Console. Click on the Authentication left menu item and go to the Password Policy tab. Choose the policy you want to add in the right side drop down list box. This will add the policy in the table on the screen. Choose the parameters for the policy. Hit the Save button to store your changes. Password Policy After saving your policy, user registration and the Update Password required action will enforce your new policy. An example of a user failing the policy check: Failed Password Policy If the password policy is updated, an Update Password action must be set for every user. An automatic trigger is scheduled as a future enhancement. 6.1.1. Password Policy Types Here's an explanation of each policy type: Hashing Algorithm Passwords are not stored as clear text. Instead they are hashed using standard hashing algorithms before they are stored or validated. Supported values are pbkdf2, pbkdf2-sha256 and pbkdf2-sha512. Hashing Iterations This value specifies the number of times a password will be hashed before it is stored or verified. The default value is 27,500. This hashing is done in the rare case that a hacker gets access to your password database. Once they have access to the database, they can reverse engineer user passwords. The industry recommended value for this parameter changes every year as CPU power improves. A higher hashing iteration value takes more CPU power for hashing, and can impact performance. You'll have to weigh what is more important to you: performance or protecting your passwords stores. There may be more cost effective ways of protecting your password stores. Digits The number of digits required to be in the password string. Lowercase Characters The number of lower case letters required to be in the password string. Uppercase Characters The number of upper case letters required to be in the password string. Special Characters The number of special characters like '?!#%USD' required to be in the password string. Not Username When set, the password is not allowed to be the same as the username. Regular Expression Define one or more regular expression patterns (defined in java.util.regex.Pattern ) that passwords must match. Expire Password The number of days for which the password is valid. After the number of days has expired, the user is required to change their password. Not Recently Used This policy saves a history of passwords. The number of old passwords stored is configurable. When a user changes their password they cannot use any stored passwords. Password Blacklist This policy checks if a given password (converted to lowercase) is contained in a blacklist file, which is potentially a very large file. Password blacklists are UTF-8 plain-text files with Unix line endings where every line represents a blacklisted password. All passwords in the blacklist must be lowercased to facilitate case-insensitive comparison. The file name of the blacklist file must be provided as the password policy value, e.g. 10_million_password_list_top_1000000.txt . Blacklist files are resolved against USD{jboss.server.data.dir}/password-blacklists/ by default. This path can be customized via the keycloak.password.blacklists.path system property, or the blacklistsPath property of the passwordBlacklist policy SPI configuration. 6.2. OTP Policies Red Hat Single Sign-On has a number of policies you can set up for your FreeOTP or Google Authenticator One-Time Password generator. Click on the Authentication left menu item and go to the OTP Policy tab. OTP Policy Any policies you set here will be used to validate one-time passwords. When configuring OTP, FreeOTP and Google Authenticator can scan a QR code that is generated on the OTP set up page that Red Hat Single Sign-On has. The bar code is also generated from information configured on the OTP Policy tab. 6.2.1. TOTP vs. HOTP There are two different algorithms to choose from for your OTP generators. Time Based (TOTP) and Counter Based (HOTP). For TOTP, your token generator will hash the current time and a shared secret. The server validates the OTP by comparing all the hashes within a certain window of time to the submitted value. So, TOTPs are valid only for a short window of time (usually 30 seconds). For HOTP a shared counter is used instead of the current time. The server increments the counter with each successful OTP login. So, valid OTPs only change after a successful login. TOTP is considered a little more secure because the matchable OTP is only valid for a short window of time while the OTP for HOTP can be valid for an indeterminate amount of time. HOTP is much more user friendly as the user won't have to hurry to enter in their OTP before the time interval is up. With the way Red Hat Single Sign-On has implemented TOTP this distinction becomes a little more blurry. HOTP requires a database update every time the server wants to increment the counter. This can be a performance drain on the authentication server when there is heavy load. So, to provide a more efficient alternative, TOTP does not remember passwords used. This bypasses the need to do any DB updates, but the downside is that TOTPs can be re-used in the valid time interval. For future versions of Red Hat Single Sign-On it is planned that you will be able to configure whether TOTP checks older OTPs in the time interval. 6.2.2. TOTP Configuration Options OTP Hash Algorithm Default is SHA1, more secure options are SHA256 and SHA512. Number of Digits How many characters is the OTP? Short means more user friendly as it is less the user has to type. More means more security. Look Ahead Window How many intervals ahead should the server try and match the hash? This exists so just in case the clock of the TOTP generator or authentication server get out of sync. The default value of 1 is usually good enough. For example, if the time interval for a new token is every 30 seconds, the default value of 1 means that it will only accept valid tokens in that 30 second window. Each increment of this config value will increase the valid window by 30 seconds. OTP Token Period Time interval in seconds during which the server will match a hash. Each time the interval passes, a new TOTP will be generated by the token generator. 6.2.3. HOTP Configuration Options OTP Hash Algorithm Default is SHA1, more secure options are SHA256 and SHA512. Number of Digits How many characters is the OTP? Short means more user friendly as it is less the user has to type. More means more security. Look Ahead Window How many counters ahead should the server try and match the hash? The default value is 1. This exists to cover the case where the user's counter gets ahead of the server's. This can often happen as users often increment the counter manually too many times by accident. This value really should be increased to a value of 10 or so. Initial Counter What is the value of the initial counter? 6.3. Authentication Flows An authentication flow is a container for all authentications, screens, and actions that must happen during login, registration, and other Red Hat Single Sign-On workflows. If you go to the admin console Authentication left menu item and go to the Flows tab, you can view all the defined flows in the system and what actions and checks each flow requires. 6.3.1. Built-in flows Red Hat Single Sign-On comes with a certain number of built-in flows. These flows cannot be modified, but the requirements can be modified to suit your needs. This section does a walk-through of the built-in browser login flow. In the left drop-down list select browser to come to the screen shown below: Browser Flow If you hover over the tooltip (the tiny question mark) to the right of the flow selection list, this will describe what the flow is and does. The Auth Type column is the name of authentication or action that will be executed. If an authentication is indented this means it is in a sub-flow and may or may not be executed depending on the behavior of its parent. The Requirement column is a set of radio buttons which define whether or not the action will execute. Let's describe what each radio button means in this context. 6.3.1.1. Execution requirements Required For a flow to be evaluated as successful, all required elements in the flow must evaluate as successful. This means that all Required elements in the flow must be sequentially executed, from top to bottom, unless one of the elements causes the flow to fail. However, this is only true for the current flow. Any Required element within a sub-flow is only processed if that sub-flow is entered. Alternative When a flow contains only Alternative elements, only a single element must evaluate as successful for the flow to evaluate as successful. Because the Required flow elements within a flow are sufficient to mark a flow as successful, any Alternative flow element within a flow that contains Required flow elements will never be executed. In this case, they are functionally Disabled . Disabled Any Disabled element is not evaluated and does not count to mark a flow as successful. Conditional This requirement type can only be set on sub-flows. A Conditional sub-flow can contain a "Condition" execution. These "Condition" executions must evaluate as logical statements. If all "Condition" executions evaluate as true then the Conditional sub-flow acts as Required . If not, the Conditional sub-flow acts as Disabled . If no "Condition" execution is set, the Conditional sub-flow acts as Disabled . If a flow contains "Condition" executions and is not set to Conditional , the "Condition" executions are not evaluated, and can be considered functionally Disabled . This is better described in an example. Let's walk through the browser authentication flow. The first authentication type is Cookie . When a user successfully logs in for the first time, a session cookie is set. If this cookie has already been set, then this authentication type is successful. In this case, since the cookie provider returned success and each execution at this level of the flow is alternative , no other execution is executed and this results in a successful login. The second execution of the flow looks at the Kerberos execution. This authenticator is disabled by default and will be skipped. The third execution is the Identity Provider Redirector . It can be configured through the Actions > Config link to automatically redirect to another IdP for identity brokering . The execution is a sub-flow called Forms . Since this sub-flow is marked as alternative it will not be executed if the Cookie authentication type passed. This sub-flow contains additional authentication type that needs to be executed. The executions for this sub-flow are loaded and the same processing logic occurs. The first execution in the Forms sub-flow is the Username Password Form. This authentication type renders the username and password page. It is marked as required so the user must enter in a valid username and password. The second execution in the Forms sub-flow is a new sub-flow: the Browser - Conditional OTP sub-flow. Since this sub-flow is conditional , whether it is executed depends on the result of the evaluation of the Condition - User Configured execution. If it is, the executions for this sub-flow are loaded and the same processing logic occurs The execution is the Condition - User Configured . This checks if the other executions in the flow are configured for the user. Meaning that the Browser - Conditional OTP sub-flow will only be executed if the user has an OTP credential configured. The final execution is the OTP Form . This is marked as required , but because of the setup in the conditional subflow, it will only be run if the user has an OTP credential set up. If he doesn't, the user will not see an OTP form. 6.3.2. Creating flows This section explains in greater depth how flows work, and how to create your own flows. Note that there are important functionality and security considerations when designing your own flow. A badly created flow could either let no one log in, let users in with less verification than you would like, or simply result in an error. To create a flow, you can either: Copy and then modify an existing flow. To do this select an existing flow (for example the Browser flow), and press the Copy button. This will then ask you to set a name for the new flow, before creating it. Create a new flow from scratch. To do this press the New button. Since this is the more general case, we will use this for our example. When creating a new flow, you will have to create a top level flow Create a top level flow With the following options: Alias The name of the flow. Description The description you can set to the flow. Top Level Flow Type The type of flow. the type client is used only for the authentication of clients (applications). For all other cases choose generic . Once the flow is created, in addition to the New and Copy buttons, you now have, Delete , Add execution and Add flow . An empty new flow What a flow finally does is determined by the structure of the flow and sub-flows, the executions in those flows, and the requirements set on the sub-flows and the executions. Executions can be added with the Add execution button. Executions can have a wide variety of actions, from sending a reset email to validating an OTP. If you hover over the tooltip (the tiny question mark) to Provider , this will describe what the execution does. Adding an authentication execution These can be divided into automatic executions and interactive executions . Automatic executions are similar to the Cookie execution, and will automatically perform their action when they are encountered in the flow. Interactive executions will halt the flow, usually to get some user input. Executions that execute successfully will get the success status. This is important, because this is part of whether a flow is successful or not. For example, an empty Browser flow would not allow anyone to log in. For that it would need at least one execution that successfully evaluates, for example a Username Password Form that is corrected filled and submitted. Sub-flows can be added in top level flow with the Add flow button, which opens a Create Execution Flow page that is very similar to the Create Top Level Form page. The only difference is that the Flow Type can be either generic (like before), or form . The form type is used to construct a sub-flow that generates a single form for the user, like what is done for the built-in Registration flow. Sub-flows are a special type of execution that evaluate as successful depending on how the executions they contain evaluate (and this includes the evaluation of their contained sub-flows). And the logic of this evaluation depends on the Requirement of each execution and sub-flow. Fully understanding this requires a more complete explanation of how requirements work when evaluating a flow, and this also applies to sub-flows. Refer to the execution requirements section above for more details. Note that after adding an execution, you should check that the Requirement is set to the correct value. Even if there is only a single possible Requirement, it can happen that it is not set. When constructing a flow, all elements added to the flow will have an Actions menu on the right-hand side. All elements added to the flow have a Delete option in this menu to remove it from the flow. Executions can contain a Config menu option to configure the execution, as is the case for the Identity Provider Redirector . Sub-flows can also have executions and sub-flows added to them, with their Add execution and Add flow menu options. Finally, since the order of execution is important, you can move executions and sub-flows up and down within their respective flows with the up and down buttons that are set to left of their name. 6.3.3. Creating a password-less browser login flow To illustrate the creation of flows, this section describes the creation of a more advanced browser login flow. The purpose of this flow is to allow a user to choose between logging in in a password-less manner using WebAuthn , and a two-factor authentication with password and OTP. The flow to create is similar to the standard browser login, but diverges when reaching the username selection. Instead of copying the flow however, you'll be creating the flow from the start: Select a realm, click on Authentication link Select "new", and give the new flow a distinctive Alias, i.e. "Browser Password-less" Select "Add execution", and using the drop-down select "Cookie". After pressing "Save", set its Requirement to Alternative . Select "Add execution", and using the drop-down select "Kerberos". Select "Add execution", and using the drop-down select "Identity Provider Redirector". After pressing "Save", set its Requirement to Alternative . Select "Add flow", and choose an representative Alias, e.g. "Forms". After pressing "Save", set its Requirement to Alternative . The common part with the browser flow Using the Actions menu on the right-hand side of the "Forms" subflow, select "Add execution". Using the drop-down select "Username Form". After pressing "Save", set its Requirement to Required . The Username form is similar to "Browser" flow's Username Password Form, but only asks for a username, allowing a user to perform a password-less login. However, note that this inevitably allows a user enumeration attack on your Red Hat Single Sign-On server. This is an unavoidable security risk for the convenience, so the flow should make sure that an attacker cannot just have to guess a password to be able to enter. Using the Actions menu on the right-hand side of the "Forms" subflow, select "Add flow". Choose an representative Alias, e.g. "Authentication". After pressing "Save", set its Requirement to Required . Using the Actions menu on the right-hand side of the "Authentication" subflow, select "Add execution". Using the drop-down select "Webauthn Passwordless Authenticator". After pressing "Save", set its Requirement to Alternative . Using the Actions menu on the right-hand side of the "Authentication" subflow, select "Add flow". Choose an representative Alias, e.g. "Password with OTP". After pressing "Save", set its Requirement to Alternative . Using the Actions menu on the right-hand side of the "Password with OTP" subflow, select "Add execution". Using the drop-down select "Password Form". After pressing "Save", set its Requirement to Required. Using the Actions menu on the right-hand side of the "Password with OTP" subflow, select "Add execution". Using the drop-down select "OTP Form". After pressing "Save", set its Requirement to Required. In the "Bindings" menu, change the browser flow from "Browser" to "Browser Password-less" The final flow that is produced is the following: A password-less browser login After entering the username, the way this flow works is the following: If the user has any WebAuthn passwordless credentials recorded, that user will be able to use any of them to log in directly. This is the password-less login. The user can instead select "Password with OTP". The user can do this because the "WebAuthn Passwordless" execution and the "Password with OTP" flow are set to Alternative . Were they set to Required the user would have to enter WebAuthn, password, and OTP. If the user selects Try another way link on the screen with "WebAuthn passwordless" authentication, the user can choose between "Password" and "Security Key" (WebAuthn passwordless). When selecting the password, the user will need to continue and log in with the assigned OTP as well. If the user has no WebAuthn credentials, he will have to first enter his password, and then his OTP. If the user has no OTP credential, he will be asked to record one. It is important to note that since the WebAuthn Passwordless execution is set to Alternative instead of Required , this flow will never ask the user to register a WebAuthn credential. For a user to have a Webauthn credential, that user must have a required action added by an administrator. This is done first by making sure that the Webauthn Register Passwordless required action is enabled in the realm (see the WebAuthn documentation), and then by setting the required action by using the Credential Reset part of a user's Credentials management menu. Creating a more advanced flow such as this one can have some subtle side effects. For example, if you were to enable the ability to reset the password for the user, then this would be accessible from the password form. In the default "Reset Credentials" flow, the user has to enter his username. Since he's already entered his username earlier in the "Browser Password-less" flow, this would be unnecessary for Red Hat Single Sign-On, and a sub-optimal in
sudo kadmin.local addprinc -randkey HTTP/www.mydomain.org@MYDOMAIN.ORG ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG [domain_realm] .mydomain.org = MYDOMAIN.ORG mydomain.org = MYDOMAIN.ORG // Obtain accessToken in your application. KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) servletReq.getUserPrincipal(); AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken(); // Retrieve kerberos credential from accessToken and deserialize it String serializedGssCredential = (String) accessToken.getOtherClaims(). get(org.keycloak.common.constants.KerberosConstants.GSS_DELEGATION_CREDENTIAL); GSSCredential deserializedGssCredential = org.keycloak.common.util.KerberosSerializationUtils. deserializeCredential(serializedGssCredential); // Create GSSContext to call other kerberos-secured services GSSContext context = gssManager.createContext(serviceName, krb5Oid, deserializedGssCredential, GSSContext.DEFAULT_LIFETIME); emailAddress=(.*?)(?:,|USD) <security-realms> <security-realm name="ssl-realm"> <server-identities> <ssl> <keystore path="servercert.jks" relative-to="jboss.server.config.dir" keystore-password="servercert password"/> </ssl> </server-identities> <authentication> <truststore path="truststore.jks" relative-to="jboss.server.config.dir" keystore-password="truststore password"/> </authentication> </security-realm> </security-realms> <subsystem xmlns="urn:jboss:domain:undertow:10.0"> . <server name="default-server"> <https-listener name="default" socket-binding="https" security-realm="ssl-realm" verify-client="REQUESTED"/> </server> </subsystem> <spi name="x509cert-lookup"> <default-provider>haproxy</default-provider> <provider name="haproxy" enabled="true"> <properties> <property name="sslClientCert" value="SSL_CLIENT_CERT"/> <property name="sslCertChainPrefix" value="CERT_CHAIN"/> <property name="certificateChainLength" value="10"/> </properties> </provider> </spi> <spi name="x509cert-lookup"> <default-provider>apache</default-provider> <provider name="apache" enabled="true"> <properties> <property name="sslClientCert" value="SSL_CLIENT_CERT"/> <property name="sslCertChainPrefix" value="CERT_CHAIN"/> <property name="certificateChainLength" value="10"/> </properties> </provider> </spi> <spi name="x509cert-lookup"> <default-provider>nginx</default-provider> <provider name="nginx" enabled="true"> <properties> <property name="sslClientCert" value="ssl-client-cert"/> <property name="sslCertChainPrefix" value="USELESS"/> <property name="certificateChainLength" value="2"/> </properties> </provider> </spi> server { ssl_client_certificate trusted-ca-list-for-client-auth.pem; ssl_verify_client optional_no_ca; ssl_verify_depth 2; location / { proxy_set_header ssl-client-cert USDssl_client_escaped_cert; } } <profile> <subsystem xmlns="urn:jboss:domain:logging:3.0"> <logger category="org.keycloak.authentication.authenticators.x509"> <level name="TRACE"/> </logger> <logger category="org.keycloak.services.x509"> <level name="TRACE"/> </logger> WARNING: Don't use RequestDumpingHandler or TRACE logging in production. curl https://[host][:port]/auth/realms/master/protocol/openid-connect/token --insecure --data "grant_type=password&scope=openid profile&username=&password=&client_id=CLIENT_ID&client_secret=CLIENT_SECRET" -E /path/to/client_cert.crt --key /path/to/client_cert.key