client initiated account linking

This commit is contained in:
Bill Burke 2017-03-09 16:21:37 -05:00
parent 3fcbb4af9b
commit bb53f5c8bc
6 changed files with 141 additions and 1 deletions

View file

@ -52,6 +52,13 @@ Regardless the identity provider you are creating, you'll see the following conf
|Enabled
|Turn the provider on/off
|Hide On Login Page
|When this switch is on, this provider will not be shown as a login option on the login page. Clients can still request to use this provider by using the 'kc_idp_hint' parameter in the URL they use to request a login.
|Link Only
|When this switch is on, this provider cannot be used to login users and will not be shown as an option on the login page. Existing accounts can still be linked with this provider though.
|Store Tokens
|Whether or not to store the token received from the identity provider.

View file

@ -19,6 +19,8 @@ An application must have authenticated with {{book.project.name}} and have recei
will need to have the `broker` client-level role `read-token` set. This means that the user must have a role mapping for this role
and the client application must have that role within its scope.
In this case, given that you are accessing a protected service in {{book.project.name}}, you need to send the access token issued by {{book.project.name}} during the user authentication.
In the broker configuration page you can automatically assign this role to newly imported users by turning on the `Stored Tokens Readable` switch.
These external tokens can be re-established by either logging in again through the provider, or using the client initiated account linking API.

View file

@ -4,6 +4,9 @@
.. link:server_development/topics/admin-rest-api.adoc[Admin REST API]
.. link:server_development/topics/themes.adoc[Themes]
.. link:server_development/topics/custom-attributes.adoc[Custom User Attributes]
.. link:server_development/topics/identity-brokering.adoc[Identity Brokering APIs]
... link:server_development/topics/identity-brokering/tokens.adoc[Retrieving External IDP Tokens]
... link:server_development/topics/identity-brokering/account-linking.adoc[Client Initiated Account Linking]
{% if book.community %}
.. link:server_development/topics/providers.adoc[Service Provider Interfaces (SPI)]
.. link:server_development/topics/extensions.adoc[Extending Server]

View file

@ -0,0 +1,8 @@
== Identity Brokering APIs
{{book.project.name}} can delegate authentication to a parent IDP for login. A typical example of this is the case
where you want users to be able to login through a social provider like Facebook or Google. {{book.project.name}}
also allows you to link existing accounts to a brokered IDP. This section talks about some APIs that your applications
can use as it pertains to identity brokering.

View file

@ -0,0 +1,94 @@
=== Client Initiated Account Linking
Some applications want to integrate with social providers like Facebook, but do not want to provide an option to login via
these social providers. {{book.project.name}} offers a browser-based API that applications can use to link an existing
user account to a specific external IDP. This is called client initiated account linking.
The way it works is that the application forward's the user's browser to a URL on the {{book.project.name}} server requesting
that it wants to link the user's account to a specific external provider (i.e. Facebook). The server
initiates a login with the external provider. The browser logs in at the external provider and is redirected
back to the auth server. The auth server establishes the link and redirects back to the application with a confirmation.
There are some preconditions that must be met by the client application before it can initiate this protocol:
* The desired identity provider must be configured and enabled for the user's realm in the admin console.
* The application must already be logged in as an existing user via the OIDC protocol
* The user must have an `account.manage-account` or `account.manage-account-links` role mapping.
* The application must be granted the scope for those roles within its access token
* The application must have access to its access token as it needs information within it to generate the redirect URL.
To initiate the login, the application must fabricate a URL and redirect the user's browser to this URL. The URL looks like this:
[source,java]
----
{auth-server-root}/auth/realms/{realm}/broker/{provider}/linking?client_id={id}&redirect_uri={uri}&nonce={nonce}&hash={hash}
----
Here's a description of each path and query param:
provider::
This is the provider alias of the external IDP that you defined in the `Identity Provider` section of the admin console.
client_id::
This is the OIDC client id of your application. When you registered the application as a client in the admin console,
you had to specify this client id.
redirect_uri::
This is the application callback URL you want to redirect to after the account link is established. It must be a valid
client redirect URI pattern. In other words, it must match one of the valid URL patterns you defined when you registered
the client in the admin console.
nonce::
This is a random string that your application must generate
hash::
This is a Base64 URL encoded hash. This hash is generated by Base64 URL encoding a SHA_256 hash of `nonce` + `token.getSessionState()` + `token.getClientSession()` + `provider`
The token variable are obtained from the OIDC access token. Basically you are hashing the random nonce, the user session id, the client session id, and the identity
provider alias you want to access.
Here's an example of Java Servlet code that generates the URL to establish the account link.
[source,java]
----
KeycloakSecurityContext session = (KeycloakSecurityContext) httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
AccessToken token = session.getToken();
String clientSessionId = token.getClientSession();
String nonce = UUID.randomUUID().toString();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
String input = nonce + token.getSessionState() + clientSessionId + provider;
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
String hash = Base64Url.encode(check);
request.getSession().setAttribute("hash", hash);
String redirectUri = ...;
String accountLinkUrl = KeycloakUriBuilder.fromUri(authServerRootUrl)
.path("/auth/realms/{realm}/broker/{provider}/link")
.queryParam("nonce", nonce)
.queryParam("hash", hash)
.queryParam("client_id", token.getIssuedFor())
.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
----
Why is this hash included? We do this so that the auth server is guaranteed to know that the client application initiated the request and no other rogue app
just randomly asked for a user account to be linked to a specific provider. The auth server will first check to see if the user is logged in by checking the SSO
cookie set at login. It will then try to regenerate the hash based on the current login and match it up to the hash sent by the application.
After the account has been linked, the auth server will redirect back to the `redirect_uri`. If there is a problem servicing the link request,
the auth server may or may not redirect back to the `redirect_uri`. The browser may just end up at an error page instead of being redirected back
to the application. If there is an error condition and the auth server deems it safe enough to redirect back to the client app, an additional
`error` query parameter will be appended to the `redirect_uri`.
[WARNING]
While this API guarantees that the application initiated the request, it does not completely prevent CSRF attacks for this operation. The application
is still responsible for guarding against CSRF attacks target at itself.
==== Refreshing External Tokens
If you are using the external token generated by logging into the provider (i.e. a Facebook or Github token), you can refresh this token by re-initiating the account linking API.

View file

@ -0,0 +1,26 @@
=== Retrieving External IDP Tokens
{{book.project.name}} allows you to store tokens and responses from the authentication process with the external IDP.
For that, you can use the `Store Token` configuration option on the IDP's settings page.
Application code can retrieve these tokens and responses to pull in extra user information, or to securely invoke requests on the external IDP.
For example, an application might want to use the Google token to invoke on other Google services and REST APIs.
To retrieve a token for a particular identity provider you need to send a request as follows:
[source,java]
----
GET /auth/realms/{realm}/broker/{provider_alias}/token HTTP/1.1
Host: localhost:8080
Authorization: Bearer {keycloak_access_token}
----
An application must have authenticated with {{book.project.name}} and have received an access token. This access token
will need to have the `broker` client-level role `read-token` set. This means that the user must have a role mapping for this role
and the client application must have that role within its scope.
In this case, given that you are accessing a protected service in {{book.project.name}}, you need to send the access token issued by {{book.project.name}} during the user authentication.
In the broker configuration page you can automatically assign this role to newly imported users by turning on the `Stored Tokens Readable` switch.
These external tokens can be re-established by either logging in again through the provider, or using the client initiated account linking API.