diff --git a/securing_apps/keycloak-images/exchange-idp-apply-policy.png b/securing_apps/keycloak-images/exchange-idp-apply-policy.png new file mode 100644 index 0000000000..06e20b3fa6 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-idp-apply-policy.png differ diff --git a/securing_apps/keycloak-images/exchange-idp-client-policy.png b/securing_apps/keycloak-images/exchange-idp-client-policy.png new file mode 100644 index 0000000000..9a88cb4ac2 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-idp-client-policy.png differ diff --git a/securing_apps/keycloak-images/exchange-idp-permission-set.png b/securing_apps/keycloak-images/exchange-idp-permission-set.png new file mode 100644 index 0000000000..adc644b2f6 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-idp-permission-set.png differ diff --git a/securing_apps/keycloak-images/exchange-idp-permission-setup.png b/securing_apps/keycloak-images/exchange-idp-permission-setup.png new file mode 100644 index 0000000000..5e1586360f Binary files /dev/null and b/securing_apps/keycloak-images/exchange-idp-permission-setup.png differ diff --git a/securing_apps/keycloak-images/exchange-idp-permission-unset.png b/securing_apps/keycloak-images/exchange-idp-permission-unset.png new file mode 100644 index 0000000000..23aa116dd2 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-idp-permission-unset.png differ diff --git a/securing_apps/keycloak-images/exchange-users-apply-policy.png b/securing_apps/keycloak-images/exchange-users-apply-policy.png new file mode 100644 index 0000000000..f0db3aea86 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-users-apply-policy.png differ diff --git a/securing_apps/keycloak-images/exchange-users-client-policy.png b/securing_apps/keycloak-images/exchange-users-client-policy.png new file mode 100644 index 0000000000..b04bfb3c01 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-users-client-policy.png differ diff --git a/securing_apps/keycloak-images/exchange-users-permission-set.png b/securing_apps/keycloak-images/exchange-users-permission-set.png new file mode 100644 index 0000000000..7220740bcb Binary files /dev/null and b/securing_apps/keycloak-images/exchange-users-permission-set.png differ diff --git a/securing_apps/keycloak-images/exchange-users-permission-setup.png b/securing_apps/keycloak-images/exchange-users-permission-setup.png new file mode 100644 index 0000000000..031aa52c0c Binary files /dev/null and b/securing_apps/keycloak-images/exchange-users-permission-setup.png differ diff --git a/securing_apps/keycloak-images/exchange-users-permission-unset.png b/securing_apps/keycloak-images/exchange-users-permission-unset.png new file mode 100644 index 0000000000..d51f5ae147 Binary files /dev/null and b/securing_apps/keycloak-images/exchange-users-permission-unset.png differ diff --git a/securing_apps/rhsso-images/exchange-idp-apply-policy.png b/securing_apps/rhsso-images/exchange-idp-apply-policy.png new file mode 100644 index 0000000000..d9a5838d70 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-idp-apply-policy.png differ diff --git a/securing_apps/rhsso-images/exchange-idp-client-policy.png b/securing_apps/rhsso-images/exchange-idp-client-policy.png new file mode 100644 index 0000000000..d7edf04159 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-idp-client-policy.png differ diff --git a/securing_apps/rhsso-images/exchange-idp-permission-set.png b/securing_apps/rhsso-images/exchange-idp-permission-set.png new file mode 100644 index 0000000000..305d7a5ec3 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-idp-permission-set.png differ diff --git a/securing_apps/rhsso-images/exchange-idp-permission-setup.png b/securing_apps/rhsso-images/exchange-idp-permission-setup.png new file mode 100644 index 0000000000..84818f410d Binary files /dev/null and b/securing_apps/rhsso-images/exchange-idp-permission-setup.png differ diff --git a/securing_apps/rhsso-images/exchange-idp-permission-unset.png b/securing_apps/rhsso-images/exchange-idp-permission-unset.png new file mode 100644 index 0000000000..34b5d0e520 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-idp-permission-unset.png differ diff --git a/securing_apps/rhsso-images/exchange-users-apply-policy.png b/securing_apps/rhsso-images/exchange-users-apply-policy.png new file mode 100644 index 0000000000..1b7a3c84e4 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-users-apply-policy.png differ diff --git a/securing_apps/rhsso-images/exchange-users-client-policy.png b/securing_apps/rhsso-images/exchange-users-client-policy.png new file mode 100644 index 0000000000..df038d3589 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-users-client-policy.png differ diff --git a/securing_apps/rhsso-images/exchange-users-permission-set.png b/securing_apps/rhsso-images/exchange-users-permission-set.png new file mode 100644 index 0000000000..2086423405 Binary files /dev/null and b/securing_apps/rhsso-images/exchange-users-permission-set.png differ diff --git a/securing_apps/rhsso-images/exchange-users-permission-setup.png b/securing_apps/rhsso-images/exchange-users-permission-setup.png new file mode 100644 index 0000000000..40e3bb2a7f Binary files /dev/null and b/securing_apps/rhsso-images/exchange-users-permission-setup.png differ diff --git a/securing_apps/rhsso-images/exchange-users-permission-unset.png b/securing_apps/rhsso-images/exchange-users-permission-unset.png new file mode 100644 index 0000000000..01c9a4521d Binary files /dev/null and b/securing_apps/rhsso-images/exchange-users-permission-unset.png differ diff --git a/securing_apps/topics/token-exchange/token-exchange.adoc b/securing_apps/topics/token-exchange/token-exchange.adoc index 99091856c2..cdb705d04a 100644 --- a/securing_apps/topics/token-exchange/token-exchange.adoc +++ b/securing_apps/topics/token-exchange/token-exchange.adoc @@ -65,7 +65,7 @@ scope:: is requesting. It is not implemented at this time but will be once {project_name} has better support for scopes in general. -NOTE: We currently only support OpenID Connect and OAuth exchanges. Support for SAML based clients and identity providers may be added in the feature depending on user demand. +NOTE: We currently only support OpenID Connect and OAuth exchanges. Support for SAML based clients and identity providers may be added in the future depending on user demand. A successful response from an exchange invocation will return the HTTP 200 response code with a content type that depends on the `requested-token-type` and `requested_issuer` the client asks for. OAuth requested token types will return @@ -115,6 +115,7 @@ claims and permissions within the access token. Other reasons this type of exch need to perform a "permission downgrade" where your app needs to invoke on a less trusted app and you don't want to propagate your current access token. +[[_client_to_client_permission]] ==== Granting Permission for the Exchange Clients that want to exchange tokens for a different client need to be authorized in the admin console to do so. @@ -147,12 +148,12 @@ defined. .Apply Client Policy image:{project_images}/exchange-target-client-exchange-apply-policy.png[] -Your client now has permission to invoke. If you do not do this correct, you will get a 403 Forbidden response if you +Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you try to make an exchange. ==== Making the Request -When your client is exchange an existing token for a token targeting another client, you must use the `audience` parameter. +When your client is exchanging an existing token for a token targeting another client, you must use the `audience` parameter. This parameter must be the client identifier for the target client that you configured in the admin console. [source,bash] @@ -167,10 +168,187 @@ curl -X POST \ http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token ---- + The `subject_token` parameter must be an access token for the target realm. If your `requested_token_type` parameter is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here's -an example JSON response you get back from this call: +an example JSON response you get back from this call. +[source,json] +---- +{ + "access_token" : "....", + "refresh_token" : "....", + "expires_in" : 3600 +} +---- + +=== Internal Token to External Token Exchange + +You can exchange a realm token for an externl token minted by an external identity provider. This external identity provider +must be configured within the `Identity Provider` section of the admin console. Currently only OAuth/OpenID Connect based external +identity providers are supported, this includes all social providers. {project_name} does not perform a backchannel exchange to the external provider. So if the account +is not linked, you will not be able to get the external token. To be able to obtain an external token one of +these conditions must be met: + +* The user must have logged in with the external identity provider at least once +* The user must have linked with the external identity provider through the User Account Service +* The user account was linked through the external identity provider using link:{developerguide_link}[Client Initiated Account Linking] API. + +Finally, the external identity provider must have been configured to store tokens, or, one of the above actions must +have been performed with the same user session as the internal token you are exchanging. + +If the account is not linked, the exchange response will contain a link you can use to establish it. This is +discussed more in the <<_internal_external_making_request, Making the Request>> section. + +[[_grant_permission_external_exchange]] +==== Granting Permission for the Exchange + +Internal to external token exchange requests will be denied with a 403, Forbidden response until you grant +permission for the calling client to exchange tokens with the external identity provider. To grant permission +to the client you must go to the identity provider's configuration page to the `Permissions` tab. + +.Identity Provider Permission +image:{project_images}/exchange-idp-permission-unset.png[] + +Toggle the `Permissions Enabled` switch to true. + +.Identity Provier Permission +image:{project_images}/exchange-idp-permission-set.png[] + +You should see a `token-exchange` link on the page. Click that to start defining the permission. It will bring you +to this page. + +.Identity Provider Exchange Permission Setup +image:{project_images}/exchange-idp-permission-setup.png[] + +You'll have to define a policy for this permission. Click the `Authorization` link, go to the `Policies` tab and create +a `Client` Policy. + +.Client Policy Creation +image:{project_images}/exchange-idp-client-policy.png[] + +Here you enter in the starting client, that is the authenticated client that is requesting a token exchange. After you +create this policy, go back to the identity providers's `token-exchange` permission and add the client policy you just +defined. + +.Apply Client Policy +image:{project_images}/exchange-idp-apply-policy.png[] + +Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you +try to make an exchange. + +[[_internal_external_making_request]] +==== Making the Request + +When your client is exchanging an existing internal token to an external one, you must provide the +`requested_issuer` parameter. The parameter must be the alias of a configured identity provider. + +[source,bash] +---- +curl -X POST \ + -d "client_id=starting-client" \ + -d "client_secret=geheim" \ + --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=...." \ + --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" + -d "requested_issuer=google" \ + http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token +---- + + +The `subject_token` parameter must be an access token for the target realm. The `requested_token_type` parameter +must be `urn:ietf:params:oauth:token-type:access_token` or left blank. No other requested token type is supported +at this time. Here's +an example successful JSON response you get back from this call. + +[source,json] +---- +{ + "access_token" : "....", + "expires_in" : 3600 + "account-link-url" : "https://...." +} +---- + +If the external identity provider is not linked for whatever reason, you will get an HTTP 400 response code with +this JSON document: + +[source,json] +---- +{ + "error" : "....", + "error_description" : "..." + "account-link-url" : "https://...." +} +---- + +The `error` claim will be either `token_expired` or `not_linked`. The `account-link-url` claim is provided +so that the client can perform link:{developerguide_link}[Client Initiated Account Linking]. Most (all?) +providers requiring linking through browser OAuth protocol. With the `account-link-url` just add a `redirect_uri` +query parameter to it and you can forward browsers to perform the link. + +=== External Token to Internal Token Exchange + +You can trust and exchange external tokens minted by external identity providers for internal tokens. This can be +used to bridge between realms or just to trust tokens from your social provider. It works similarly to an identity provider +browser login in that a new user is imported into your realm if it doesn't exist. + +NOTE: The current limitation on external token exchanges is that if the external token maps to an existing user an + exchange will not be allowed unless the existing user already has an account link to the external identity + provider. + +When the exchange is complete, a user session will be created within the realm, and you will receive an access +and or refresh token depending on the `requested_toke_type` parameter value. You should note that this new +user session will remain active until it times out or until you call the logout endpoint of the realm passing this +new access token. + +These types of changes required a configured identity provider in the admin console. + +NOTE: Only OIDC identity providers are support at this time. Validate signatures switch is required and you must + provide either the public key to validate external token signatures, or a valid key URL to lookup key + identifiers contained in the token. + + +==== Granting Permission for the Exchange + +Before external token exchanges can be done, you must grant permission for the calling client to make the exchange. This +permission is granted in the same manner as <<_grant_permission_external_exchange, interal to external permission is granted>>. + +If you also provide an `audience` parameter whose value points to a different client other than the calling one, you +must also grant the calling client permission to exchange to the target client specific in the `audience` parameter. How +to do this is <<_client_to_client_permission, discussed earlier>> in this section. + +==== Making the Request + +When your client is exchanging an existing internal token to an external one, you must provide the +`subject_issuer` parameter. This parameter must be the alias of a configured identity provider. The +`subject_token_type` parameter must be specified and either be an identity token, `urn:ietf:params:oauth:token-type:id_token`, +or a JWT access token type `urn:ietf:params:oauth:token-type:jwt:access_token`. The `subject_token` must be a JWT +irregardless and must be signed using Json Web Signatures. + +By default, the internal token minted will use the calling client to determine what's in the token using the protocol +mappers defined for the calling client. Alternatively, you can specify a different target client using the `audience` +parameter. + +[source,bash] +---- +curl -X POST \ + -d "client_id=starting-client" \ + -d "client_secret=geheim" \ + --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=...." \ + -d "subject_issuer=myOidcProvider" \ + --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:id_token" + -d "audience=target-client" \ + http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token +---- + + +If your `requested_token_type` parameter +is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here's +an example JSON response you get back from this call. + +[source,json] ---- { "access_token" : "....", @@ -180,35 +358,104 @@ an example JSON response you get back from this call: ---- -=== Internal Token to External Token Exchange - -==== Granting Permission for the Exchange - -==== Making the Request - - -=== External Token to Internal Token Exchange - -==== Granting Permission for the Exchange - -==== Making the Request - - -=== External Token to External Token Exchange - === Impersonation +For internal and external token exchanges, the client can request on behalf of a user to impersonate a different user. +For example, you may have an admin application that needs to impersonate a user so that a support engineer can debug +a problem. + + ==== Granting Permission for the Exchange +The user that the subject token represents must have permission to impersonate other users. See the +link:{adminguide_link}[{adminguide_name}] on how to enable this permission. It can be done through a role or through +fine grain admin permissions. + + ==== Making the Request +Make the request as described in other chapters except additionally specify the `request_subject` parameter. The +value of this parameter must be a username or user id. + +[source,bash] +---- +curl -X POST \ + -d "client_id=starting-client" \ + -d "client_secret=geheim" \ + --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=...." \ + --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" \ + -d "audience=target-client" \ + -d "requested_subject=wburke" \ + http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token +---- === Direct Naked Impersonation +You can make an internal token exchange request without providing a `subject_token`. This is called a direct +naked impersonation because it places a lot of trust in a client as that client can impersonate any user in the realm. +You might need this to bridge for applications where it is impossible to obtain a subject token to exchange. For example, +you may be integrating a legacy application that performs login directly with LDAP. In that case, the legacy app +is able to authenticate users itself, but not able to obtain a token. + +WARNING: It is very risky to enable direct naked impersonation for a client. If the client's credentials are ever + stolen, that client can impersonate any user in the system. + ==== Granting Permission for the Exchange +If the `audience` parameter is provided, then the calling client must have permission to exchange to the client. How +to set this up is discussed earlier in this chapter. + +Additionaly, the calling client must be granted permission to impersonate users. In the admin console, go to the +`Users` screen and click on the `Permissions` tab. + +.Users Permission +image:{project_images}/exchange-users-permission-unset.png[] + +Toggle the `Permissions Enabled` switch to true. + +.Identity Provier Permission +image:{project_images}/exchange-users-permission-set.png[] + +You should see a `impersonation` link on the page. Click that to start defining the permission. It will bring you +to this page. + +.Users Impersonation Permission Setup +image:{project_images}/exchange-users-permission-setup.png[] + +You'll have to define a policy for this permission. Click the `Authorization` link, go to the `Policies` tab and create +a `Client` Policy. + +.Client Policy Creation +image:{project_images}/exchange-users-client-policy.png[] + +Here you enter in the starting client, that is the authenticated client that is requesting a token exchange. After you +create this policy, go back to the users' `impersonation` permission and add the client policy you just +defined. + +.Apply Client Policy +image:{project_images}/exchange-users-apply-policy.png[] + +Your client now has permission to impersonate users. If you do not do this correctly, you will get a 403 Forbidden response if you +try to make this type of exchange. + +NOTE: Public clients are not allowed to do direct naked impersonations. + + ==== Making the Request +To make the request, simply specify the `requested_subject` parameter. This must be the username or user id of +a valid user. You can also specify an `audience` parameter if you wish. + +[source,bash] +---- +curl -X POST \ + -d "client_id=starting-client" \ + -d "client_secret=geheim" \ + --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "requested_subject=wburke" \ + http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token +----