<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body bgcolor="#FFFFFF" text="#000000">
I'm currently working on a new credential SPI that will replace
existing methods on UserProvider and UserModel, as well as replacing
UserCredentialModel, etc. This is a work in progress where we may
see multiple iterations in master. I hope to remain backward
compatible, but can't guarentee I won't break existing User
Federation Providers. Here's an initial writeup to explain things.
Credentials revolve around these 4 events that are initiated by
authentication flows, the admin console, and the account service.
<p>* Is the user configured for a specific credential type</p>
<p>* Is a credential valid</p>
<p>* What required actions must be taken for an unconfigured
credential type</p>
<p>* update a credential</p>
<p>How each of these events is resolved will depend on the
configuration of the system and these interfaces:</p>
<p>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</p>
<pre style="background-color:#ffffff;color:#000000;font-family:'Menlo';font-size:9.0pt;"><span style="color:#000080;font-weight:bold;">public interface </span>CredentialInput {
String getType();
}
</pre>
<p>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</p>
<p>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</p>
<pre style="background-color:#ffffff;color:#000000;font-family:'Menlo';font-size:9.0pt;"><span style="color:#000080;font-weight:bold;">public interface </span>CredentialInputValidator {
<span style="color:#000080;font-weight:bold;">boolean </span>supportsCredentialType(String credentialType);
<span style="color:#000080;font-weight:bold;">boolean </span>isConfiguredFor(RealmModel realm, UserModel user, String credentialType);
<span style="color:#000080;font-weight:bold;">boolean </span>isValid(RealmModel realm, UserModel user, CredentialInput input);
}
</pre>
<p>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</p>
<pre style="background-color:#ffffff;color:#000000;font-family:'Menlo';font-size:9.0pt;"><span style="color:#000080;font-weight:bold;">public interface </span>CredentialInputUpdater {
<span style="color:#000080;font-weight:bold;">boolean </span>supportsCredentialType(String credentialType);
Set<String> requiredActionsFor(RealmModel realm, UserModel user, String credentialType);
<span style="color:#000080;font-weight:bold;">void </span>updateCredential(RealmModel realm, UserModel user, CredentialInput input);
}
</pre>
<p>Two different types of components will be able to implement these
interfaces. UserStorageProviders (user federation) and
CredentialProviders. CredentialProviders are components
configured at the realm level. CredentialProviders are
responsible for managing one or more types of credential types and
are the bridge between CredentialInput and where the credential is
stored. UserStorageProvider is always asked first whether it can
complete the requested action, then CredentialProviders are
queried in order of their priority.</p>
<p>Each UserStorageProvider and/or CredentialProvider can implement
the OnUserCache callback interface discussed in my previous custom
caching email. This allows each credential type to decide whether
it will be cached or not along with the user. For example, HOTP
cannot be cached.</p>
<p>So, for example, there will be a KeycloakMobileOTPProvider. This
deals with Google Authenticator and FreeOTP as well as storing
these things within Keycloak storage, it also looks at the OTP
policy of the realm to determine how to update and store the OTP
secret and stuff. There is also a KeycloakPasswordProvider which
hooks into Keycloak storage and the PasswordPolicies set up by the
realm. When a user is cached, the KeycloakPasswordProvider will
add the hashed password to the user cache, the
KeycloakMobileOTPProvider will add the OTP secret to cache if its
not HOTP and needs to maintain a counter.</p>
<p>Let's walk through an authentication flow, specificaly for OTP.</p>
<p>1. Authenticator calls
KeycloakSession.users().isConfiguredFor(realm, user, "OTP"). If
the user was loaded by a UserStorageProvider and that provider
implements the CredentialInputValidator interface,
isConfiguredFor() is called on that. If that returns false, each
CredentialProvider is iterated on to call isConfiguredFor().</p>
<p>2. If OTP is required and not configured for the user, the
Authenticator then calls
KeycloakSession.users().requiredActionsFor(...). Again,
UserStorageProvider is queried first, then the
CredneitalProviders. The first provider that returns a non-empty
set will end the query and the set of required actions will be
returned.</p>
<p>3a. Let's say that in this particular example, the generic OTP
Requried Action screen is invoked. In that case, this required
action provider callsKeycloakSession.users().updateCredential.
The first UserStorageProvider or CredentialProvider that can
handle this credential type will save the credential.<br>
</p>
<p>3b. If OTP is configured for user, the OTP is obtained by the
Authenticator and KeycloakSession.users().isValid() method is
called. Again, UserStorageProvider first, then each
CredentialProvider. Each provider is queried until one returns
true or the list is exhausted. FYI, This algorithm allows for
multiple OTP authenticators per user.</p>
<p>** Admin console and Account Service UIs **</p>
<p>Like we do for other components, the UserStorageProvider or
CredentialProvider can optionally provide a list of
ProviderConfigProperties for the admin console and/or account
serviceso that it can create a credential for a specific user.
There will be separate property lists for admin console and
account service. If a specific custom screen is desired, I'm
pretty sure we can just allow the develoepr to plug in their own
$routeProvider for the admin console. We don't have a pluggable
mechanism for the account service yet (or a way to generic render
either). This will need to be developed eventually.<br>
</p>
<p><br>
</p>
</body>
</html>