Keycloak
92 строки · 5.4 Кб
1
2=== Client initiated account linking
3
4Some applications want to integrate with social providers like Facebook, but do not want to provide an option to login via
5these social providers. {project_name} offers a browser-based API that applications can use to link an existing
6user account to a specific external IDP. This is called client-initiated account linking. Account linking can only be initiated by OIDC applications.
7
8The way it works is that the application forwards the user's browser to a URL on the {project_name} server requesting
9that it wants to link the user's account to a specific external provider (i.e. Facebook). The server
10initiates a login with the external provider. The browser logs in at the external provider and is redirected
11back to the server. The server establishes the link and redirects back to the application with a confirmation.
12
13There are some preconditions that must be met by the client application before it can initiate this protocol:
14
15* The desired identity provider must be configured and enabled for the user's realm in the admin console.
16* The user account must already be logged in as an existing user via the OIDC protocol
17* The user must have an `account.manage-account` or `account.manage-account-links` role mapping.
18* The application must be granted the scope for those roles within its access token
19* The application must have access to its access token as it needs information within it to generate the redirect URL.
20
21To initiate the login, the application must fabricate a URL and redirect the user's browser to this URL. The URL looks like this:
22
23[source,subs="attributes+"]
24----
25/{auth-server-root}{kc_realms_path}/{realm}/broker/{provider}/link?client_id={id}&redirect_uri={uri}&nonce={nonce}&hash={hash}
26----
27
28Here's a description of each path and query param:
29
30provider::
31This is the provider alias of the external IDP that you defined in the `Identity Provider` section of the admin console.
32
33client_id::
34This is the OIDC client id of your application. When you registered the application as a client in the admin console,
35you had to specify this client id.
36
37redirect_uri::
38This is the application callback URL you want to redirect to after the account link is established. It must be a valid
39client redirect URI pattern. In other words, it must match one of the valid URL patterns you defined when you registered
40the client in the admin console.
41
42nonce::
43This is a random string that your application must generate
44
45hash::
46This is a Base64 URL encoded hash. This hash is generated by Base64 URL encoding a SHA_256 hash of `nonce` + `token.getSessionState()` + `token.getIssuedFor()` + `provider`.
47The token variable are obtained from the OIDC access token. Basically you are hashing the random nonce, the user session id, the client id, and the identity
48provider alias you want to access.
49
50Here's an example of Java Servlet code that generates the URL to establish the account link.
51
52
53[source,java,subs="attributes+"]
54----
55KeycloakSecurityContext session = (KeycloakSecurityContext) httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
56AccessToken token = session.getToken();
57String clientId = token.getIssuedFor();
58String nonce = UUID.randomUUID().toString();
59MessageDigest md = null;
60try {
61md = MessageDigest.getInstance("SHA-256");
62} catch (NoSuchAlgorithmException e) {
63throw new RuntimeException(e);
64}
65String input = nonce + token.getSessionState() + clientId + provider;
66byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
67String hash = Base64Url.encode(check);
68request.getSession().setAttribute("hash", hash);
69String redirectUri = ...;
70String accountLinkUrl = KeycloakUriBuilder.fromUri(authServerRootUrl)
71.path("{kc_realms_path}/{realm}/broker/{provider}/link")
72.queryParam("nonce", nonce)
73.queryParam("hash", hash)
74.queryParam("client_id", clientId)
75.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
76----
77
78Why 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
79just 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
80cookie 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.
81
82After the account has been linked, the auth server will redirect back to the `redirect_uri`. If there is a problem servicing the link request,
83the 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
84to 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
85`error` query parameter will be appended to the `redirect_uri`.
86
87[WARNING]
88While this API guarantees that the application initiated the request, it does not completely prevent CSRF attacks for this operation. The application
89is still responsible for guarding against CSRF attacks target at itself.
90
91==== Refreshing external tokens
92
93If 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.
94
95