GSaha567/seq_level_training_data
052
1text,length,is_long_context,metric_val,label_metric2"[[_auth_spi]]3== Authentication SPI4 5{project_name} includes a range of different authentication mechanisms: kerberos, password, otp and others.6These mechanisms may not meet all of your requirements and you may want to plug in your own custom ones.7{project_name} provides an authentication SPI that you can use to write new plugins.8The admin console supports applying, ordering, and configuring these new mechanisms.9 10{project_name} also supports a simple registration form.11Different aspects of this form can be enabled and disabled i.e.12Recaptcha support can be turned off and on.13The same authentication SPI can be used to add another page to the registration flow or reimplement it entirely.14There's also an additional fine-grained SPI you can use to add specific validations and user extensions to the built in registration form.15 16A required action in {project_name} is an action that a user has to perform after he authenticates.17After the action is performed successfully, the user doesn't have to perform the action again.18{project_name} comes with some built in required actions like ""reset password"". This action forces the user to change their password after they have logged in.19You can write and plug in your own required actions.20 21WARNING: If your authenticator or required action implementation is using some user attributes as the metadata attributes for linking/establishing the user identity,22then please make sure that users are not able to edit the attributes and the corresponding attributes are read-only. See the details in the link:{adminguide_link}#_read_only_user_attributes[Threat model mitigation chapter].23 24=== Terms25 26To first learn about the Authentication SPI, let's go over some of the terms used to describe it.27 28Authentication Flow::29 A flow is a container for all authentications that must happen during login or registration.30 If you go to the admin console authentication page, you can view all the defined flows in the system and what authenticators they are made up of.31 Flows can contain other flows.32 You can also bind a new different flow for browser login, direct grant access, and registration.33 34Authenticator::35 An authenticator is a pluggable component that hold the logic for performing the authentication or action within a flow.36 It is usually a singleton.37 38Execution::39 An execution is an object that binds the authenticator to the flow and the authenticator to the configuration of the authenticator.40 Flows contain execution entries.41 42Execution Requirement::43 Each execution defines how an authenticator behaves in a flow.44 The requirement defines whether the authenticator is enabled, disabled, conditional, required, or an alternative.45 An alternative requirement means that the authenticator is enough to validate the flow it's in, but isn't necessary.46 For example, in the built-in browser flow, cookie authentication, the Identity Provider Redirector, and the set of all authenticators in the47 forms subflow are all alternative. As they are executed in a sequential top-to-bottom order, if one of them is successful, the flow is48 successful, and any following execution in the flow (or sub-flow) is not evaluated.49 50Authenticator Config::51 This object defines the configuration for the Authenticator for a specific execution within an authentication flow.52 Each execution can have a different config.53 54Required Action::55 After authentication completes, the user might have one or more one-time actions he must complete before he is allowed to login.56 The user might be required to set up an OTP token generator or reset an expired password or even accept a Terms and Conditions document.57 58=== Algorithm Overview59 60Let's talk about how this all works for browser login.61Let's assume the following flows, executions and sub flows.62[source]63----64 65Cookie - ALTERNATIVE66Kerberos - ALTERNATIVE67Forms subflow - ALTERNATIVE68 Username/Password Form - REQUIRED69 Conditional OTP subflow - CONDITIONAL70 Condition - User Configured - REQUIRED71 OTP Form - REQUIRED72----73 74In the top level of the form we have 3 executions of which all are alternatively required.75This means that if any of these are successful, then the others do not have to execute.76The Username/Password form is not executed if there is an SSO Cookie set or a successful Kerberos login.77Let's walk through the steps from when a client first redirects to keycloak to authenticate the user.78 79. The OpenID Connect or SAML protocol provider unpacks relevant data, verifies the client and any signatures.80 It creates an AuthenticationSessionModel.81 It looks up what the browser flow should be, then starts executing the flow.82. The flow looks at the cookie execution and sees that it is an alternative.83 It loads the cookie provider.84 It checks to see if the cookie provider requires that a user already be associated with the authentication session.85 Cookie provider does not require a user.86 If it did, the flow would abort and the user would see an error screen.87 Cookie provider then executes.88 Its purpose is to see if there is an SSO cookie set.89 If there is one set, it is validated and the UserSessionModel is verified and associated with the AuthenticationSessionModel.90 The Cookie provider returns a success() status if the SSO cookie exists and is validated.91 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.92 If there is no SSO cookie, the cookie provider returns with a status of attempted(). This means there was no error condition, but no success either.93 The provider tried, but the request just wasn't set up to handle this authenticator.94. Next the flow looks at the Kerberos execution.95 This is also an alternative.96 The kerberos provider also does not require a user to be already set up and associated with the AuthenticationSessionModel so this provider is executed.97 Kerberos uses the SPNEGO browser protocol.98 This requires a series of challenge/responses between the server and client exchanging negotiation headers.99 The kerberos provider does not see any negotiate header, so it assumes that this is the first interaction between the server and client.100 It therefore creates an HTTP challenge response to the client and sets a forceChallenge() status.101 A forceChallenge() means that this HTTP response cannot be ignored by the flow and must be returned to the client.102 If instead the provider returned a challenge() status, the flow would hold the challenge response until all other alternatives are attempted.103 So, in this initial phase, the flow would stop and the challenge response would be sent back to the browser.104 If the browser then responds with a successful negotiate header, the provider associates the user with the AuthenticationSession and the flow ends because the rest of the executions on this level of the flow are all alternatives.105 Otherwise, again, the kerberos provider sets an attempted() status and the flow continues.106. The next execution is a subflow called Forms.107 The executions for this subflow are loaded and the same processing logic occurs.108. The first execution in the Forms subflow is the UsernamePassword provider.109 This provider also does not require for a user to already be associated with the flow.110 This provider creates a challenge HTTP response and sets its status to challenge(). This execution is required, so the flow honors this challenge and sends the HTTP response back to the browser.111 This response is a rendering of the Username/Password HTML page.112 The user enters in their username and password and clicks submit.113 This HTTP request is directed to the UsernamePassword provider.114 If the user entered an invalid username or password, a new challenge response is created and a status of failureChallenge() is set for this execution.115 A failureChallenge() means that there is a challenge, but that the flow should log this as an error in the error log.116 This error log can be used to lock accounts or IP Addresses that have had too many login failures.117 If the username and password is valid, the provider associated the UserModel with the AuthenticationSessionModel and returns a status of success().118. The next execution is a subflow called Conditional OTP. The executions for this subflow are loaded and the same processing logic occurs. Its Requirement is119 Conditional. This means that the flow will first evaluate all conditional executors that it contains. Conditional executors are authenticators that120 implement `ConditionalAuthenticator`, and must implement the method `boolean matchCondition(AuthenticationFlowContext context)`. A conditional subflow will121 call the `matchCondition` method of all conditional executions it contains, and if all of them evaluate to true, it will act as if it was a required subflow. If122 not, it will act as if it was a disabled subflow. Conditional authenticators are only used for this purpose, and are not used as authenticators.123 This means that even if the conditional authenticator evaluates to ""true"", then this will not mark a flow or subflow as successful. For example,124 a flow containing only a Conditional subflow with only a conditional authenticator will never allow a user to log in.125. The first execution of the Conditional OTP subflow is the Condition - User Configured.126 This provider requires that a user has been associated with the flow.127 This requirement is satisfied because the UsernamePassword provider already associated the user with the flow.128 This provider's `matchCondition` method will evaluate the `configuredFor` method for all other Authenticators in its current subflow. If the subflow contains129 executors with their Requirement set to required, then the `matchCondition` method will only evaluate to true if all the required authenticators' `configuredFor`130 method evaluate to true. Otherwise, the `matchCondition` method will evaluate to true if any alternative authenticator evaluates to true.131. The next execution is the OTP Form.132 This provider also requires that a user has been associated with the flow.133 This requirement is satisfied because the UsernamePassword provider already associated the user with the flow.134 Since a user is required for this provider, the provider is also asked if the user is configured to use this provider.135 If user is not configured, then the flow will then set up a required action that the user must perform after authentication is complete.136 For OTP, this means the OTP setup page. If the user is configured, he will be asked to enter his otp code. In our scenario, because of the conditional137 sub-flow, the user will never see the OTP login page, unless the Conditional OTP subflow is set to Required.138. After the flow is complete, the authentication processor creates a UserSessionModel and associates it with the AuthenticationSessionModel.139 It then checks to see if the user is required to complete any required actions before logging in.140. First, each required action's evaluateTriggers() method is called.141 This allows the required action provider to figure out if there is some state that might trigger the action to be fired.142 For example, if your realm has a password expiration policy, it might be triggered by this method.143. Each required action associated with the user that has its requiredActionChallenge() method called.144 Here the provider sets up an HTTP response which renders the page for the required action.145 This is done by setting a challenge status.146. If the required action is ultimately successful, then the required action is removed from the user's required actions list.147. After all required actions have been resolved, the user is finally logged in.148 149[[_auth_spi_walkthrough]]150=== Authenticator SPI Walk Through151 152In this section, we'll take a look at the Authenticator interface.153For this, we are going to implement an authenticator that requires that a user enter in the answer to a secret question like ""What is your mother's maiden name?"".154This example is fully implemented and contained in the examples/providers/authenticator directory of the demo distribution of {project_name}.155 156To create an authenticator, you must at minimum implement the org.keycloak.authentication.AuthenticatorFactory and Authenticator interfaces.157The Authenticator interface defines the logic. The AuthenticatorFactory is responsible for creating instances of an Authenticator.158They both extend a more generic Provider and ProviderFactory set of interfaces that other {project_name} components like User Federation do.159 160Some authenticators, like the CookieAuthenticator don't rely on a Credential that the user has or knows to authenticate the user. 161However, some authenticators, such as the PasswordForm authenticator or the OTPFormAuthenticator rely on the user inputting some162information and verifying that information against some information in the163database. For the PasswordForm for example, the authenticator will verify the hash of the password against a hash stored in the database, while the164OTPFormAuthenticator will verify the OTP received against the one generated from the shared secret stored in the database.165 166These types of authenticators are called CredentialValidators, and will require you to implement a few more classes:167 168* A class that extends org.keycloak.credential.CredentialModel, and that can generate the correct format of the credential in the database169* A class implementing the org.keycloak.credential.CredentialProvider and interface, and a class implementing its CredentialProviderFactory factory interface.170 171The SecretQuestionAuthenticator we'll see in this walk through is a CredentialValidator, so we'll see how to implement all these classes.172 173==== Packaging Classes and Deployment174 175You will package your classes within a single jar.176This jar must contain a file named `org.keycloak.authentication.AuthenticatorFactory` and must be contained in the `META-INF/services/` directory of your jar.177This file must list the fully qualified class name of each AuthenticatorFactory implementation you have in the jar.178For example:179 180[source,java]181----182org.keycloak.examples.authenticator.SecretQuestionAuthenticatorFactory183org.keycloak.examples.authenticator.AnotherProviderFactory184----185 186This services/ file is used by {project_name} to scan the providers it has to load into the system.187 188To deploy this jar, just copy it to the providers directory.189 190==== Extending the CredentialModel class191 192In {project_name}, credentials are stored in the database in the Credentials table. It has the following structure:193 194----195-----------------------------196| ID |197-----------------------------198| user_ID |199-----------------------------200| credential_type |201-----------------------------202| created_date |203-----------------------------204| user_label |205-----------------------------206| secret_data |207-----------------------------208| credential_data |209-----------------------------210| priority |211-----------------------------212----213 214Where:215 216* `ID` is the primary key of the credential.217* `user_ID` is the foreign key linking the credential to a user.218* `credential_type` is a string set during the creation that must reference an existing credential type.219* `created_date` is the creation timestamp (in long format) of the credential.220* `user_label` is the editable name of the credential by the user221* `secret_data` contains a static json with the information that cannot be transmitted outside of {project_name}222* `credential_data` contains a json with the static information of the credential that can be shared in the admin console or via the REST API.223* `priority` defines how ""preferred"" a credential is for a user, to determine which credential to present when a user has multiple choices.224 225As the secret_data and credential_data fields are designed to contain json, it is up to you to determine how to structure, read and write into226these fields, allowing you a lot of flexibility.227 228For this example, we are going to use a very simple credential data, containing only the question asked to the user:229 230[source]231----232{233 ""question"":""aQuestion""234}235----236 237with an equally simple secret data, containing only the secret answer:238 239[source]240----241{242 ""answer"":""anAnswer""243}244----245 246Here the answer will be kept in plain text in the database for the sake of simplicity, but it would also be possible to have a salted hash for the answer,247as is the case for passwords in {project_name}. In this case, the secret data would also have to contain a field for the salt, and the credential data information248about the algorithm such as the type of algorithm used and the number of iterations used. For more details you can consult the implementation of the249`org.keycloak.models.credential.PasswordCredentialModel` class.250 251In our case we create the class `SecretQuestionCredentialModel`:252 253 254[source,java]255----256public class SecretQuestionCredentialModel extends CredentialModel {257 public static final String TYPE = ""SECRET_QUESTION"";258 259 private final SecretQuestionCredentialData credentialData;260 private final SecretQuestionSecretData secretData;261----262 263Where `TYPE` is the credential_type we write in the database. For consistency, we make sure that this String is always the one referenced when264getting the type for this credential. The classes `SecretQuestionCredentialData` and `SecretQuestionSecretData` are used to marshal and unmarshal the json:265 266[source,java]267----268public class SecretQuestionCredentialData {269 270 private final String question;271 272 @JsonCreator273 public SecretQuestionCredentialData(@JsonProperty(""question"") String question) {274 this.question = question;275 }276 277 public String getQuestion() {278 return question;279 }280}281----282 283[source,java]284----285public class SecretQuestionSecretData {286 287 private final String answer;288 289 @JsonCreator290 public SecretQuestionSecretData(@JsonProperty(""answer"") String answer) {291 this.answer = answer;292 }293 294 public String getAnswer() {295 return answer;296 }297}298----299 300To be fully usable, the `SecretQuestionCredentialModel` objects must both contain the raw json data from its parent class,301and the unmarshalled objects in its own attributes. This leads us to create a method which reads from a simple CredentialModel,302such as is created when reading from the database, to make a `SecretQuestionCredentialModel`:303 304[source,java]305----306private SecretQuestionCredentialModel(SecretQuestionCredentialData credentialData, SecretQuestionSecretData secretData) {307 this.credentialData = credentialData;308 this.secretData = secretData;309}310 311public static SecretQuestionCredentialModel createFromCredentialModel(CredentialModel credentialModel){312 try {313 SecretQuestionCredentialData credentialData = JsonSerialization.readValue(credentialModel.getCredentialData(), SecretQuestionCredentialData.class);314 SecretQuestionSecretData secretData = JsonSerialization.readValue(credentialModel.getSecretData(), SecretQuestionSecretData.class);315 316 SecretQuestionCredentialModel secretQuestionCredentialModel = new SecretQuestionCredentialModel(credentialData, secretData);317 secretQuestionCredentialModel.setUserLabel(credentialModel.getUserLabel());318 secretQuestionCredentialModel.setCreatedDate(credentialModel.getCreatedDate());319 secretQuestionCredentialModel.setType(TYPE);320 secretQuestionCredentialModel.setId(credentialModel.getId());321 secretQuestionCredentialModel.setSecretData(credentialModel.getSecretData());322 secretQuestionCredentialModel.setCredentialData(credentialModel.getCredentialData());323 return secretQuestionCredentialModel;324 } catch (IOException e){325 throw new RuntimeException(e);326 }327}328----329 330And a method to create a `SecretQuestionCredentialModel` from the question and answer:331 332[source,java]333----334private SecretQuestionCredentialModel(String question, String answer) {335 credentialData = new SecretQuestionCredentialData(question);336 secretData = new SecretQuestionSecretData(answer);337}338 339public static SecretQuestionCredentialModel createSecretQuestion(String question, String answer) {340 SecretQuestionCredentialModel credentialModel = new SecretQuestionCredentialModel(question, answer);341 credentialModel.fillCredentialModelFields();342 return credentialModel;343}344 345private void fillCredentialModelFields(){346 try {347 setCredentialData(JsonSerialization.writeValueAsString(credentialData));348 setSecretData(JsonSerialization.writeValueAsString(secretData));349 setType(TYPE);350 setCreatedDate(Time.currentTimeMillis());351 } catch (IOException e) {352 throw new RuntimeException(e);353 }354}355----356 357==== Implementing a CredentialProvider358 359As with all Providers, to allow {project_name} to generate the CredentialProvider, we require a CredentialProviderFactory. For this requirement we create360the SecretQuestionCredentialProviderFactory, whose `create` method will be called when a SecretQuestionCredentialProvider is asked for:361 362[source,java]363----364public class SecretQuestionCredentialProviderFactory implements CredentialProviderFactory<SecretQuestionCredentialProvider> {365 366 public static final String PROVIDER_ID = ""secret-question"";367 368 @Override369 public String getId() {370 return PROVIDER_ID;371 }372 373 @Override374 public CredentialProvider create(KeycloakSession session) {375 return new SecretQuestionCredentialProvider(session);376 }377}378----379 380The CredentialProvider interface takes a generic parameter that extends a CredentialModel. In our case we to use the SecretQuestionCredentialModel we created:381 382[source,java]383----384public class SecretQuestionCredentialProvider implements CredentialProvider<SecretQuestionCredentialModel>, CredentialInputValidator {385 private static final Logger logger = Logger.getLogger(SecretQuestionCredentialProvider.class);386 387 protected KeycloakSession session;388 389 public SecretQuestionCredentialProvider(KeycloakSession session) {390 this.session = session;391 }392 393 private UserCredentialStore getCredentialStore() {394 return session.userCredentialManager();395 }396----397 398We also want to implement the CredentialInputValidator interface, as this allows {project_name} to know that this provider can also be used to validate a399credential for an Authenticator. For the CredentialProvider interface, the first method that needs to be implemented is the `getType()` method. This will simply400return the `SecretQuestionCredentialModel`'s TYPE String:401 402[source,java]403----404@Override405public String getType() {406 return SecretQuestionCredentialModel.TYPE;407}408----409 410The second method is to create a `SecretQuestionCredentialModel` from a `CredentialModel`. For this method we simply call the existing static method411from `SecretQuestionCredentialModel`:412 413[source,java]414----415@Override416public SecretQuestionCredentialModel getCredentialFromModel(CredentialModel model) {417 return SecretQuestionCredentialModel.createFromCredentialModel(model);418}419----420 421Finally, we have the methods to create a credential and delete a credential. These methods call the KeycloakSession's `userCredentialManager`, which422is responsible for knowing where to read or write the credential, for example local storage or federated storage.423 424[source,java]425----426@Override427public CredentialModel createCredential(RealmModel realm, UserModel user, SecretQuestionCredentialModel credentialModel) {428 if (credentialModel.getCreatedDate() == null) {429 credentialModel.setCreatedDate(Time.currentTimeMillis());430 }431 return getCredentialStore().createCredential(realm, user, credentialModel);432}433 434@Override435public boolean deleteCredential(RealmModel realm, UserModel user, String credentialId) {436 return getCredentialStore().removeStoredCredential(realm, user, credentialId);437}438----439 440For the CredentialInputValidator, the main method to implement is the `isValid`, which tests whether a credential is valid for a441given user in a given realm. This is the method that is called by the Authenticator when it seeks to validate the user's input. Here we442simply need to check that the input String is the one recorded in the Credential:443 444[source,java]445----446@Override447public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {448 if (!(input instanceof UserCredentialModel)) {449 logger.debug(""Expected instance of UserCredentialModel for CredentialInput"");450 return false;451 }452 if (!input.getType().equals(getType())) {453 return false;454 }455 String challengeResponse = input.getChallengeResponse();456 if (challengeResponse == null) {457 return false;458 }459 CredentialModel credentialModel = getCredentialStore().getStoredCredentialById(realm, user, input.getCredentialId());460 SecretQuestionCredentialModel sqcm = getCredentialFromModel(credentialModel);461 return sqcm.getSecretQuestionSecretData().getAnswer().equals(challengeResponse);462}463----464 465The other two methods to implement are a test if the CredentialProvider supports the given credential type and a test to check466if the credential type is configured for a given user. For our case, the latter test simply means checking if the user has a credential467of the SECRET_QUESTION type:468 469[source,java]470----471@Override472public boolean supportsCredentialType(String credentialType) {473 return getType().equals(credentialType);474}475 476@Override477public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {478 if (!supportsCredentialType(credentialType)) return false;479 return !getCredentialStore().getStoredCredentialsByType(realm, user, credentialType).isEmpty();480}481----482 483==== Implementing an Authenticator484 485When implementing an authenticator that uses Credentials to authenticate a user, you should have the authenticator implement486the CredentialValidator interface. This interfaces takes a class extending a CredentialProvider as a parameter, and will487allow {project_name} to directly call the methods from the CredentialProvider. The only method that needs to be implemented is488`getCredentialProvider` method, which in our example allows the SecretQuestionAuthenticator to retrieve the SecretQuestionCredentialProvider:489 490[source,java]491----492public SecretQuestionCredentialProvider getCredentialProvider(KeycloakSession session) {493 return (SecretQuestionCredentialProvider)session.getProvider(CredentialProvider.class, SecretQuestionCredentialProviderFactory.PROVIDER_ID);494}495----496 497When implementing the Authenticator interface, the first method that needs to be implemented is the requiresUser() method.498For our example, this method must return true as we need to validate the secret question associated with the user.499A provider like kerberos would return false from this method as it can resolve a user from the negotiate header.500This example, however, is validating a specific credential of a specific user.501 502The next method to implement is the configuredFor() method.503This method is responsible for determining if the user is configured for this particular authenticator. In our case,504we can just call the method implemented in the SecretQuestionCredentialProvider505 506[source,java]507----508@Override509public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {510 return getCredentialProvider(session).isConfiguredFor(realm, user, getType(session));511}512----513 514The next method to implement on the Authenticator is setRequiredActions(). If configuredFor() returns false and our example authenticator515is required within the flow, this method will be called, but only if the associated AuthenticatorFactory's `isUserSetupAllowed` method returns true.516The setRequiredActions() method is responsible for registering any required actions that must be performed by the user.517In our example, we need to register a required action that will force the user to set up the answer to the secret question.518We will implement this required action provider later in this chapter.519Here is the implementation of the setRequiredActions() method.520 521[source,java]522----523 @Override524 public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {525 user.addRequiredAction(""SECRET_QUESTION_CONFIG"");526 }527----528 529Now we are getting into the meat of the Authenticator implementation.530The next method to implement is authenticate(). This is the initial method the flow invokes when the execution is first visited.531What we want is that if a user has answered the secret question already on their browser's machine, then the user doesn't532have to answer the question again, making that machine ""trusted"". The authenticate() method isn't responsible for processing the secret question form.533Its sole purpose is to render the page or to continue the flow.534 535[source,java]536----537@Override538public void authenticate(AuthenticationFlowContext context) {539 if (hasCookie(context)) {540 context.success();541 return;542 }543 Response challenge = context.form()544 .createForm(""secret-question.ftl"");545 context.challenge(challenge);546}547 548protected boolean hasCookie(AuthenticationFlowContext context) {549 Cookie cookie = context.getHttpRequest().getHttpHeaders().getCookies().get(""SECRET_QUESTION_ANSWERED"");550 boolean result = cookie != null;551 if (result) {552 System.out.println(""Bypassing secret question because cookie is set"");553 }554 return result;555}556----557 558The hasCookie() method checks to see if there is already a cookie set on the browser which indicates that the secret question has already been answered.559If that returns true, we just mark this execution's status as SUCCESS using the AuthenticationFlowContext.success() method and returning from the authentication() method.560 561If the hasCookie() method returns false, we must return a response that renders the secret question HTML form.562AuthenticationFlowContext has a form() method that initializes a Freemarker page builder with appropriate base information needed to build the form.563This page builder is called `org.keycloak.login.LoginFormsProvider`. The LoginFormsProvider.createForm() method loads a Freemarker template file from your login theme.564Additionally you can call the LoginFormsProvider.setAttribute() method if you want to pass additional information to the Freemarker template.565We'll go over this later.566 567Calling LoginFormsProvider.createForm() returns a JAX-RS Response object.568We then call AuthenticationFlowContext.challenge() passing in this response.569This sets the status of the execution as CHALLENGE and if the execution is Required, this JAX-RS Response object will be sent to the browser.570 571So, the HTML page asking for the answer to a secret question is displayed to the user and the user enters in the answer and clicks submit.572The action URL of the HTML form will send an HTTP request to the flow.573The flow will end up invoking the action() method of our Authenticator implementation.574 575[source,java]576----577@Override578public void action(AuthenticationFlowContext context) {579 boolean validated = validateAnswer(context);580 if (!validated) {581 Response challenge = context.form()582 .setError(""badSecret"")583 .createForm(""secret-question.ftl"");584 context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge);585 return;586 }587 setCookie(context);588 context.success();589}590----591 592If the answer is not valid, we rebuild the HTML Form with an additional error message.593We then call AuthenticationFlowContext.failureChallenge() passing in the reason for the value and the JAX-RS response.594failureChallenge() works the same as challenge(), but it also records the failure so it can be analyzed by any attack detection service.595 596If validation is successful, then we set a cookie to remember that the secret question has been answered and we call AuthenticationFlowContext.success().597 598The validation itself gets the data that was received from the form, and calls the isValid method from the SecretQuestionCredentialProvider. You'll notice599that there's a section of the code concerning getting the credential Id. This is because if {project_name} is configured to allow multiple types of alternative600authenticators, or if the user could record multiple credentials of the SECRET_QUESTION type (for example if we allowed to choose from several questions,601and we allowed the user to have answers for more than one of those questions), then {project_name} needs to know which credential is being used to log the user.602In case there is more than one credential, {project_name} allows the user to choose during the login which credential is being used, and the information is transmitted by603the form to the Authenticator.604In case the form doesn't present this information, credential id used is given by the CredentialProvider's `default getDefaultCredential` method, which will605return the ""most preferred"" credential of the correct type of the user,606 607[source,java]608----609protected boolean validateAnswer(AuthenticationFlowContext context) {610 MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();611 String secret = formData.getFirst(""secret_answer"");612 String credentialId = formData.getFirst(""credentialId"");613 if (credentialId == null || credentialId.isEmpty()) {614 credentialId = getCredentialProvider(context.getSession())615 .getDefaultCredential(context.getSession(), context.getRealm(), context.getUser()).getId();616 }617 618 UserCredentialModel input = new UserCredentialModel(credentialId, getType(context.getSession()), secret);619 return getCredentialProvider(context.getSession()).isValid(context.getRealm(), context.getUser(), input);620}621----622 623Next method is the setCookie().624This is an example of providing configuration for the Authenticator.625In this case we want the max age of the cookie to be configurable.626 627[source,java]628----629protected void setCookie(AuthenticationFlowContext context) {630 AuthenticatorConfigModel config = context.getAuthenticatorConfig();631 int maxCookieAge = 60 * 60 * 24 * 30; // 30 days632 if (config != null) {633 maxCookieAge = Integer.valueOf(config.getConfig().get(""cookie.max.age""));634 635 }636 URI uri = context.getUriInfo().getBaseUriBuilder().path(""realms"").path(context.getRealm().getName()).build();637 addCookie(context, ""SECRET_QUESTION_ANSWERED"", ""true"",638 uri.getRawPath(),639 null, null,640 maxCookieAge,641 false, true);642}643----644 645We obtain an AuthenticatorConfigModel from the AuthenticationFlowContext.getAuthenticatorConfig() method.646If configuration exists we pull the max age config out of it.647We will see how we can define what should be configured when we talk about the AuthenticatorFactory implementation.648The config values can be defined within the admin console if you set up config definitions in your AuthenticatorFactory implementation.649 650[source,java]651----652@Override653 public CredentialTypeMetadata getCredentialTypeMetadata() {654 return CredentialTypeMetadata.builder()655 .type(getType())656 .category(CredentialTypeMetadata.Category.TWO_FACTOR)657 .displayName(SecretQuestionCredentialProviderFactory.PROVIDER_ID)658 .helpText(""secret-question-text"")659 .createAction(SecretQuestionAuthenticatorFactory.PROVIDER_ID)660 .removeable(false)661 .build(session);662 }663----664 665Last method in SecretQuestionCredentialProvider class is getCredentialTypeMetadata(), which is an abstract method of CredentialProvider666interface. Each Credential provider has to provide and implement this method. The method returns an instance of CredentialTypeMetadata,667which should at least include type and category of authenticator, displayName and removable item. In this example, the builder668takes type of authenticator from method getType(), category is Two Factor (the authenticator can be used as second factor of authentication)669and removable, which is set up to false (user can't remove some previously registered credentials).670 671Other items of builder are helpText (will be shown to the user on various screens), createAction (the providerID of the required action,672which can be used by the user to create new credential) or updateAction (same as createAction, but instead of creating the new credential, it will update the credential).673 674==== Implementing an AuthenticatorFactory675 676The next step in this process is to implement an AuthenticatorFactory.677This factory is responsible for instantiating an Authenticator.678It also provides deployment and configuration metadata about the Authenticator.679 680The getId() method is just the unique name of the component.681The create() method is called by the runtime to allocate and process the Authenticator.682 683[source,java]684----685 686public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {687 688 public static final String PROVIDER_ID = ""secret-question-authenticator"";689 private static final SecretQuestionAuthenticator SINGLETON = new SecretQuestionAuthenticator();690 691 @Override692 public String getId() {693 return PROVIDER_ID;694 }695 696 @Override697 public Authenticator create(KeycloakSession session) {698 return SINGLETON;699 }700----701 702The next thing the factory is responsible for is to specify the allowed requirement switches.703While there are four different requirement types: ALTERNATIVE, REQUIRED, CONDITIONAL, DISABLED, AuthenticatorFactory implementations can limit which704requirement options are shown in the admin console when defining a flow. CONDITIONAL should only always be used for subflows, and unless there's a good705reason for doing otherwise, the requirement on a authenticator should be REQUIRED, ALTERNATIVE and DISABLED:706 707[source,java]708----709 710 private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {711 AuthenticationExecutionModel.Requirement.REQUIRED,712 AuthenticationExecutionModel.Requirement.ALTERNATIVE,713 AuthenticationExecutionModel.Requirement.DISABLED714 };715 @Override716 public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {717 return REQUIREMENT_CHOICES;718 }719----720 721The AuthenticatorFactory.isUserSetupAllowed() is a flag that tells the flow manager whether or not Authenticator.setRequiredActions() method will be called.722If an Authenticator is not configured for a user, the flow manager checks isUserSetupAllowed(). If it is false, then the flow aborts with an error.723If it returns true, then the flow manager will invoke Authenticator.setRequiredActions().724 725[source,java]726----727 728 @Override729 public boolean isUserSetupAllowed() {730 return true;731 }732----733 734The next few methods define how the Authenticator can be configured.735The isConfigurable() method is a flag which specifies to the admin console on whether the Authenticator can be configured within a flow.736The getConfigProperties() method returns a list of ProviderConfigProperty objects.737These objects define a specific configuration attribute.738 739[source,java]740----741 742 @Override743 public List<ProviderConfigProperty> getConfigProperties() {744 return configProperties;745 }746 747 private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();748 749 static {750 ProviderConfigProperty property;751 property = new ProviderConfigProperty();752 property.setName(""cookie.max.age"");753 property.setLabel(""Cookie Max Age"");754 property.setType(ProviderConfigProperty.STRING_TYPE);755 property.setHelpText(""Max age in seconds of the SECRET_QUESTION_COOKIE."");756 configProperties.add(property);757 }758----759 760Each ProviderConfigProperty defines the name of the config property.761This is the key used in the config map stored in AuthenticatorConfigModel.762The label defines how the config option will be displayed in the admin console.763The type defines if it is a String, Boolean, or other type.764The admin console will display different UI inputs depending on the type.765The help text is what will be shown in the tooltip for the config attribute in the admin console.766Read the javadoc of ProviderConfigProperty for more detail.767 768The rest of the methods are for the admin console.769getHelpText() is the tooltip text that will be shown when you are picking the Authenticator you want to bind to an execution.770getDisplayType() is the text that will be shown in the admin console when listing the Authenticator.771getReferenceCategory() is just a category the Authenticator belongs to.772 773==== Adding Authenticator Form774 775{project_name} comes with a Freemarker <<_themes,theme and template engine>>.776The createForm() method you called within authenticate() of your Authenticator class, builds an HTML page from a file within your login theme: `secret-question.ftl`.777This file should be added to the `theme-resources/templates` in your JAR, see <<_theme_resource,Theme Resource Provider>> for more details.778 779Let's take a bigger look at secret-question.ftl Here's a small code snippet:780 781[source,java]782----783 784 <form id=""kc-totp-login-form"" class=""${properties.kcFormClass!}"" action=""${url.loginAction}"" method=""post"">785 <div class=""${properties.kcFormGroupClass!}"">786 <div class=""${properties.kcLabelWrapperClass!}"">787 <label for=""totp"" class=""${properties.kcLabelClass!}"">${msg(""loginSecretQuestion"")}</label>788 </div>789 790 <div class=""${properties.kcInputWrapperClass!}"">791 <input id=""totp"" name=""secret_answer"" type=""text"" class=""${properties.kcInputClass!}"" />792 </div>793 </div>794 </form>795----796 797Any piece of text enclosed in `${}` corresponds to an attribute or template funtion.798If you see the form's action, you see it points to `${url.loginAction}`.799This value is automatically generated when you invoke the AuthenticationFlowContext.form() method.800You can also obtain this value by calling the AuthenticationFlowContext.getActionURL() method in Java code.801 802You'll also see `${properties.someValue}`.803These correspond to properties defined in your theme.properties file of our theme.804 `${msg(""someValue"")}` corresponds to the internationalized message bundles (.properties files) included with the login theme messages/ directory.805If you're just using english, you can just add the value of the `loginSecretQuestion`.806This should be the question you want to ask the user.807 808When you call AuthenticationFlowContext.form() this gives you a LoginFormsProvider instance.809If you called, `LoginFormsProvider.setAttribute(""foo"", ""bar"")`, the value of ""foo"" would be available for reference in your form as `${foo}`.810The value of an attribute can be any Java bean as well.811 812If you look at the top of the file, you'll see that we are importing a template:813 814[source,java]815----816<#import ""select.ftl"" as layout>817----818 819Importing this template, instead of the standard `template.ftl` allows {project_name} to display a dropdown box that allows the user to select820a different credential or execution.821 822[[_adding_authenticator]]823==== Adding Authenticator to a Flow824 825Adding an Authenticator to a flow must be done in the admin console.826If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.827You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.828Our hope is that the user interface is sufficiently clear so that you can determine how to create a flow and add the Authenticator. For829more details, see the `Authentication Flows` chapter in link:{adminguide_link}[{adminguide_name}] .830 831After you've created your flow, you have to bind it to the login action you want to bind it to.832If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.833 834=== Required Action Walkthrough835 836In this section we will discuss how to define a required action.837In the Authenticator section you may have wondered, ""How will we get the user's answer to the secret question entered into the system?"". As we showed in the example, if the answer is not set up, a required action will be triggered.838This section discusses how to implement the required action for the Secret Question Authenticator.839 840==== Packaging Classes and Deployment841 842You will package your classes within a single jar.843This jar does not have to be separate from other provider classes but it must contain a file named `org.keycloak.authentication.RequiredActionFactory` and must be contained in the `META-INF/services/` directory of your jar.844This file must list the fully qualified classname of each RequiredActionFactory implementation you have in the jar.845For example:846 847[source,java]848----849org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory850----851 852This services/ file is used by {project_name} to scan the providers it has to load into the system.853 854To deploy this jar, just copy it to the `standalone/deployments` directory.855 856==== Implement the RequiredActionProvider857 858Required actions must first implement the RequiredActionProvider interface.859The RequiredActionProvider.requiredActionChallenge() is the initial call by the flow manager into the required action.860This method is responsible for rendering the HTML form that will drive the required action.861 862[source,java]863----864 865 @Override866 public void requiredActionChallenge(RequiredActionContext context) {867 Response challenge = context.form().createForm(""secret_question_config.ftl"");868 context.challenge(challenge);869 870 }871----872 873You see that RequiredActionContext has similar methods to AuthenticationFlowContext.874The form() method allows you to render the page from a Freemarker template.875The action URL is preset by the call to this form() method.876You just need to reference it within your HTML form.877I'll show you this later.878 879The challenge() method notifies the flow manager that a required action must be executed.880 881The next method is responsible for processing input from the HTML form of the required action.882The action URL of the form will be routed to the RequiredActionProvider.processAction() method883 884[source,java]885----886 887 @Override888 public void processAction(RequiredActionContext context) {889 String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst(""answer""));890 UserCredentialValueModel model = new UserCredentialValueModel();891 model.setValue(answer);892 model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);893 context.getUser().updateCredentialDirectly(model);894 context.success();895 }896----897 898The answer is pulled out of the form post.899A UserCredentialValueModel is created and the type and value of the credential are set.900Then UserModel.updateCredentialDirectly() is invoked.901Finally, RequiredActionContext.success() notifies the container that the required action was successful.902 903==== Implement the RequiredActionFactory904 905This class is really simple.906It is just responsible for creating the required action provider instance.907 908[source,java]909----910 911public class SecretQuestionRequiredActionFactory implements RequiredActionFactory {912 913 private static final SecretQuestionRequiredAction SINGLETON = new SecretQuestionRequiredAction();914 915 @Override916 public RequiredActionProvider create(KeycloakSession session) {917 return SINGLETON;918 }919 920 921 @Override922 public String getId() {923 return SecretQuestionRequiredAction.PROVIDER_ID;924 }925 926 @Override927 public String getDisplayText() {928 return ""Secret Question"";929 }930----931 932The getDisplayText() method is just for the admin console when it wants to display a friendly name for the required action.933 934==== Enable Required Action935 936The final thing you have to do is go into the admin console.937Click on the Authentication left menu.938Click on the Required Actions tab.939Click on the Register button and choose your new Required Action.940Your new required action should now be displayed and enabled in the required actions list.941 942=== Modifying/Extending the Registration Form943 944It is entirely possible for you to implement your own flow with a set of Authenticators to totally change how registration is done in {project_name}.945But what you'll usually want to do is just add a little bit of validation to the out of the box registration page.946An additional SPI was created to be able to do this.947It basically allows you to add validation of form elements on the page as well as to initialize UserModel attributes and data after the user has been registered.948We'll look at both the implementation of the user profile registration processing as well as the registration Google Recaptcha plugin.949 950==== Implementation FormAction Interface951 952The core interface you have to implement is the FormAction interface.953A FormAction is responsible for rendering and processing a portion of the page.954Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success(). Let's first take a look at buildPage() method of the Recaptcha plugin.955 956[source,java]957----958 959 @Override960 public void buildPage(FormContext context, LoginFormsProvider form) {961 AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();962 if (captchaConfig == null || captchaConfig.getConfig() == null963 || captchaConfig.getConfig().get(SITE_KEY) == null964 || captchaConfig.getConfig().get(SITE_SECRET) == null965 ) {966 form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED));967 return;968 }969 String siteKey = captchaConfig.getConfig().get(SITE_KEY);970 form.setAttribute(""recaptchaRequired"", true);971 form.setAttribute(""recaptchaSiteKey"", siteKey);972 form.addScript(""https://www.google.com/recaptcha/api.js"");973 }974----975 976The Recaptcha buildPage() method is a callback by the form flow to help render the page.977It receives a form parameter which is a LoginFormsProvider.978You can add additional attributes to the form provider so that they can be displayed in the HTML page generated by the registration Freemarker template.979 980The code above is from the registration recaptcha plugin.981Recaptcha requires some specific settings that must be obtained from configuration.982FormActions are configured in the exact same as Authenticators are.983In this example, we pull the Google Recaptcha site key from configuration and add it as an attribute to the form provider.984Our registration template file can read this attribute now.985 986Recaptcha also has the requirement of loading a JavaScript script.987You can do this by calling LoginFormsProvider.addScript() passing in the URL.988 989For user profile processing, there is no additional information that it needs to add to the form, so its buildPage() method is empty.990 991The next meaty part of this interface is the validate() method.992This is called immediately upon receiving a form post.993Let's look at the Recaptcha's plugin first.994 995[source,java]996----997 998 @Override999 public void validate(ValidationContext context) {1000 MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();1001 List<FormMessage> errors = new ArrayList<>();1002 boolean success = false;1003 1004 String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);1005 if (!Validation.isBlank(captcha)) {1006 AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();1007 String secret = captchaConfig.getConfig().get(SITE_SECRET);1008 1009 success = validateRecaptcha(context, success, captcha, secret);1010 }1011 if (success) {1012 context.success();1013 } else {1014 errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));1015 formData.remove(G_RECAPTCHA_RESPONSE);1016 context.validationError(formData, errors);1017 return;1018 1019 1020 }1021 }1022----1023 1024Here we obtain the form data that the Recaptcha widget adds to the form.1025We obtain the Recaptcha secret key from configuration.1026We then validate the recaptcha.1027If successful, ValidationContext.success() is called.1028If not, we invoke ValidationContext.validationError() passing in the formData (so the user doesn't have to re-enter data), we also specify an error message we want displayed.1029The error message must point to a message bundle property in the internationalized message bundles.1030For other registration extensions validate() might be validating the format of a form element, i.e.1031an alternative email attribute.1032 1033Let's also look at the user profile plugin that is used to validate email address and other user information when registering.1034 1035[source,java]1036----1037 1038 @Override1039 public void validate(ValidationContext context) {1040 MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();1041 List<FormMessage> errors = new ArrayList<>();1042 1043 String eventError = Errors.INVALID_REGISTRATION;1044 1045 if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_FIRST_NAME)))) {1046 errors.add(new FormMessage(RegistrationPage.FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME));1047 }1048 1049 if (Validation.isBlank(formData.getFirst((RegistrationPage.FIELD_LAST_NAME)))) {1050 errors.add(new FormMessage(RegistrationPage.FIELD_LAST_NAME, Messages.MISSING_LAST_NAME));1051 }1052 1053 String email = formData.getFirst(Validation.FIELD_EMAIL);1054 if (Validation.isBlank(email)) {1055 errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));1056 } else if (!Validation.isEmailValid(email)) {1057 formData.remove(Validation.FIELD_EMAIL);1058 errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));1059 }1060 1061 if (context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {1062 formData.remove(Validation.FIELD_EMAIL);1063 errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.EMAIL_EXISTS));1064 }1065 1066 if (errors.size() > 0) {1067 context.validationError(formData, errors);1068 return;1069 1070 } else {1071 context.success();1072 }1073 }1074----1075 1076As you can see, this validate() method of user profile processing makes sure that the email, first, and last name are filled in the form.1077It also makes sure that email is in the right format.1078If any of these validations fail, an error message is queued up for rendering.1079Any fields in error are removed from the form data.1080Error messages are represented by the FormMessage class.1081The first parameter of the constructor of this class takes the HTML element id.1082The input in error will be highlighted when the form is re-rendered.1083The second parameter is a message reference id.1084This id must correspond to a property in one of the localized message bundle files.1085in the theme.1086 1087After all validations have been processed then, the form flow then invokes the FormAction.success() method.1088For recaptcha this is a no-op, so we won't go over it.1089For user profile processing, this method fills in values in the registered user.1090 1091[source,java]1092----1093 1094 @Override1095 public void success(FormContext context) {1096 UserModel user = context.getUser();1097 MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();1098 user.setFirstName(formData.getFirst(RegistrationPage.FIELD_FIRST_NAME));1099 user.setLastName(formData.getFirst(RegistrationPage.FIELD_LAST_NAME));1100 user.setEmail(formData.getFirst(RegistrationPage.FIELD_EMAIL));1101 }1102----1103 1104Pretty simple implementation.1105The UserModel of the newly registered user is obtained from the FormContext.1106The appropriate methods are called to initialize UserModel data.1107 1108Finally, you are also required to define a FormActionFactory class.1109This class is implemented similarly to AuthenticatorFactory, so we won't go over it.1110 1111==== Packaging the Action1112 1113You will package your classes within a single jar.1114This jar must contain a file named `org.keycloak.authentication.FormActionFactory` and must be contained in the `META-INF/services/` directory of your jar.1115This file must list the fully qualified class name of each FormActionFactory implementation you have in the jar.1116For example:1117 1118[source]1119----1120 1121org.keycloak.authentication.forms.RegistrationProfile1122org.keycloak.authentication.forms.RegistrationRecaptcha1123----1124 1125This services/ file is used by {project_name} to scan the providers it has to load into the system.1126 1127To deploy this jar, just copy it to the `standalone/deployments` directory.1128 1129==== Adding FormAction to the Registration Flow1130 1131Adding a FormAction to a registration page flow must be done in the admin console.1132If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently defined flows.1133You cannot modify built in flows, so, to add the Authenticator we've created you have to copy an existing flow or create your own.1134I'm hoping the UI is intuitive enough so that you can figure out for yourself how to create a flow and add the FormAction.1135 1136Basically you'll have to copy the registration flow.1137Then click Actions menu to the right of the Registration Form, and pick ""Add execution"" to add a new execution.1138You'll pick the FormAction from the selection list.1139Make sure your FormAction comes after ""Registration User Creation"" by using the down buttons to move it if your FormAction isn't already listed after ""Registration User Creation"". You want your FormAction to come after user creation because the success() method of Registration User Creation is responsible for creating the new UserModel.1140 1141After you've created your flow, you have to bind it to registration.1142If you go to the Authentication menu and go to the Bindings tab you will see options to bind a flow to the browser, registration, or direct grant flow.1143 1144=== Modifying Forgot Password/Credential Flow1145 1146{project_name} also has a specific authentication flow for forgot password, or rather credential reset initiated by a user.1147If you go to the admin console flows page, there is a ""reset credentials"" flow.1148By default, {project_name} asks for the email or username of the user and sends an email to them.1149If the user clicks on the link, then they are able to reset both their password and OTP (if an OTP has been set up). You can disable automatic OTP reset by disabling the ""Reset OTP"" authenticator in the flow.1150 1151You can add additional functionality to this flow as well.1152For example, many deployments would like for the user to answer one or more secret questions in additional to sending an email with a link.1153You could expand on the secret question example that comes with the distro and incorporate it into the reset credential flow.1154 1155One thing to note if you are extending the reset credentials flow.1156The first ""authenticator"" is just a page to obtain the username or email.1157If the username or email exists, then the AuthenticationFlowContext.getUser() will return the located user.1158Otherwise this will be null.1159This form *WILL NOT* re-ask the user to enter in an email or username if the previous email or username did not exist.1160You need to prevent attackers from being able to guess valid users.1161So, if AuthenticationFlowContext.getUser() returns null, you should proceed with the flow to make it look like a valid user was selected.1162I suggest that if you want to add secret questions to this flow, you should ask these questions after the email is sent.1163In other words, add your custom authenticator after the ""Send Reset Email"" authenticator.1164 1165=== Modifying First Broker Login Flow1166 1167First Broker Login flow is used during first login with some identity provider.1168Term `First Login` means that there is not yet existing {project_name} account linked with the particular authenticated identity provider account.1169For more details about this flow see the `Identity Brokering` chapter in link:{adminguide_link}[{adminguide_name}] .1170 1171[[_client_authentication]]1172=== Authentication of clients1173 1174{project_name} actually supports pluggable authentication for https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect] client applications.1175Authentication of client (application) is used under the hood by the {project_name} adapter during sending any backchannel requests1176to the {project_name} server (like the request for exchange code to access token after successful authentication or request to refresh token).1177But the client authentication can be also used directly by you during `Direct Access grants` (represented by OAuth2 `Resource Owner Password Credentials Flow`)1178or during `Service account` authentication (represented by OAuth2 `Client Credentials Flow`).1179 1180For more details about {project_name} adapter and OAuth2 flows see link:{adapterguide_link}[{adapterguide_name}].1181 1182==== Default implementations1183 1184Actually {project_name} has 2 default implementations of client authentication:1185 1186Traditional authentication with client_id and client_secret::1187 This is default mechanism mentioned in the https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect] or https://tools.ietf.org/html/rfc6749[OAuth2] specification and {project_name} supports it since it's early days.1188 The public client needs to include `client_id` parameter with its ID in the POST request (so it's defacto not authenticated) and the confidential client needs to include `Authorization: Basic` header with the clientId and clientSecret used as username and password.1189 1190Authentication with signed JWT::1191 This is based on the https://tools.ietf.org/html/rfc7523[JWT Bearer Token Profiles for OAuth 2.0] specification.1192 The client/adapter generates the https://tools.ietf.org/html/rfc7519[JWT] and signs it with his private key.1193 The {project_name} then verifies the signed JWT with the client's public key and authenticates client based on it.1194 1195See the demo example and especially the `examples/preconfigured-demo/product-app` for the example application showing1196the application using client authentication with signed JWT.1197 1198==== Implement your own client authenticator1199 1200For plug your own client authenticator, you need to implement few interfaces on both client (adapter) and server side.