I’m trying to impersonate a user using the token exchange functionality but I always get the error that the client is not allowed to exchange.
This is the request I’m doing using the WebFlux WebClient:
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
formData.add("requested_token_type", "urn:ietf:params:oauth:token-type:access_token");
formData.add("client_id", keycloakClientId);
formData.add("requested_subject", userId);
formData.add("subject_token", token);
return WebClient.create()
.post()
.uri(authServerUrl + "/realms/" + authServerRealm + "/protocol/openid-connect/token")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.exchangeToMono(clientResponse -> clientResponse.bodyToMono(AccessTokenResponse.class));
The response I get is this one:
{"error":"access_denied","error_description":"Client not allowed to exchange"}
How do I get the token above to be exchanged:
KeycloakBuilder.builder()
.serverUrl(authServerUrl)
.realm(authServerRealm)
.grantType(OAuth2Constants.PASSWORD)
.username(keycloakAdminUsername)
.password(keycloakAdminPassword)
.clientId(keycloakClientId)
.build();
What I am doing wrong? Why does the client need to be allowed to exchange since this impersonation and not client token exchange. Does the client need to be confidential for this to be done?
In addition to Chris' answer, I also realized that I had to do an additional step: Client -> Service account roles → Client roles → in the dropdown select ‘Realm management’ and assign Impersonation role. I tried this after I installed Keycloak with fine grained permissions as Chris indicated, and following the Keycloak documentation (which sucks btw) https://www.keycloak.org/docs/15.0/securing_apps/index.html#_token-exchange
You need to grant permission (on the target client) to allow keycloakClientId to mint a token for the target client.
7.1.1 in the docs:
https://www.keycloak.org/docs/latest/securing_apps/#internal-token-to-internal-token-exchange
You will also need to start keycloak with the following flags, so that you enable fine grained permissions:
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled -Dkeycloak.profile.feature.token_exchange=enabled
Related
I have a SAAS server with microservice architecture. Authentication is done by the new Spring authorization server. For some domain situation, I want to be able to re-issue a JWT for a logged-in user without forcing the user to enter their password again to enrich their token with additional claims.
Having: Logged-in user with claim set A.
Required: Create a new token for the user with claim set B. (Without user intervention)
I'm looking for something like this:
#PostMapping("/renew")
public Authentication token() {
return jwtAuthenticationProvider.authenticate(
new BearerTokenAuthenticationToken(JwtUtil.getCurrentAuthenticationTokenValue())
);
}
Where JwtUtil.getCurrentAuthenticationTokenValue() extracts logged-in user token value from SecurityContextHolder. This setup creates no new token and returns the old one like no authentication process has been triggered.
But I cannot find a function/service that generates a new token in spring authorization server.
PS. I cannot use RefreshToken to get new AccessToken because my client is public and according to this, RefreshToken only is issued for confidential clients.
You can read about OAuth2TokenCustomizer in the docs. Here's an example of customizing the access token:
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
In your case, you could issue a new request to the authorization endpoint (e.g. GET /oauth2/authorize?...) from the client to begin the authorization_code flow with different scopes or additional request parameters and use the customizer to add whatever claims you need. Based on the information you've provided, this would be the recommended way to use the authorization server to issue new tokens.
Adding custom endpoints to perform OAuth2-related actions (such as a custom /renew endpoint) without incorporating best practices and standards from the specification(s) would not be recommended.
I have a multi-tenant application (springboot keycloak adapter + spring security) secured by Keycloak. Given the multi-tenant nature of the project, I wrote a multi-client connector which works fine.
On the official Keycloak doc, it is recommended (for multi-tenant applications) to model each tenant as a new realm, but for me it works better to have multiple clients within the same same realm. This is due to following advantages:
Client scopes, groups and other configs can be shared
Users don't need to be duplicated on N different realms
SSO login works perfectly within same realm clients (by using bearer
services +CORS)
So, everything works fine except for 1 thing, my initial SSO access_token (which is then shared across all bearer-only services by means of CORS) is kind of big (it shows all the resources - tenants - and its roles within each resource/tenant).
I'd like to limit the size of the access_token, by means of using "scopes" to restrict the roles in the token to only those meaningful to the tenant where I'm logged in at that time. For this, I'm manually firing a Request to the auth server (outside of the standard functionality provided by springboot/spring security) with the goal of manually overwriting whatever access-token exists within my app, with the new one generated by my extra request.
My "new" token request looks similar to this:
SimpleKeycloakAccount currentUserAccount = (SimpleKeycloakAccount) auth.getDetails();
String authServerUrl = currentUserAccount.getKeycloakSecurityContext().getDeployment().getAuthServerBaseUrl();
String realm = currentUserAccount.getKeycloakSecurityContext().getDeployment().getRealm();
String resource = currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceName();
String refreshToken = currentUserAccount.getKeycloakSecurityContext().getRefreshToken();
String token = currentUserAccount.getKeycloakSecurityContext().getTokenString();
Http http = new Http( new Configuration(authServerUrl, realm, resource,
currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceCredentials()
, null),
(params, headers) -> {});
String url = authServerUrl + "/realms/" + realm + "/protocol/openid-connect/token";
AccessTokenResponse response = http.<AccessTokenResponse>post(url)
.authentication()
.client()
.form()
.param("grant_type", "refresh_token")
.param("refresh_token", refreshToken)
.param("client_id", resource)
.param("client_secret", "SOME_SECRET")
.param("scope", "SOME_SCOPE_TO_RESTRICT_ROLES")
.response()
.json(AccessTokenResponse.class)
.execute();
// :) - response.getToken() and response.getRefreshToken(), contain new successfully generated tokens
My question is, how can I force my-app to change/reset the standard access-token & refresh_token obtained by the usual means, with these "custom created" tokens? or is that possible at all?
Thx for any feedback!
Further Information
To clarify more, lets analyze the behavior of a typical springboot/spring security project integrated with Keycloak:
You protect your endpoints with "roles" via configurations (either on the application.properties, or on the SecurityContext)
You know that this Spring application talks in the back channel with the Keycloak authorization server, that's how you become the access_token (But all this is a black box for the developer, you only know a Principal was created, a Security Context, Credentials; etc - everything happens behind the curtains)
Considering those 2 points above, imagine that you use an Http library to basically request a new token towards the auth server token endpoint like in the code above (yes filtered by scopes and everything). So the situation now is that though you have created a valid access_token (and refresh_token); since they were created "manually" by firing a request towards the token endpoint, this new token hasn't been "incorporated" to the application because No new Principal has been created, no new security context has been generated, etc. In other words, to the springboot application this new token is non-existent.
What I'm trying to accomplish is to tell sprinboot/spring security: "Hey pal, I know you didn't generate this token yourself, but please accept it and behave as if you'd have created it".
I hope this clarifies the intent of my question.
You can revoke a token using org.springframework.security.oauth2.provider.token.ConsumerTokenServices#revokeToken method.
On the Autorization Server:
#Resource(name="tokenServices")
ConsumerTokenServices tokenServices;
#RequestMapping(method = RequestMethod.POST, value = "/tokens/revoke/{tokenId:.*}")
#ResponseBody
public String revokeToken(#PathVariable String tokenId) {
tokenServices.revokeToken(tokenId);
return tokenId;
}
Of course, you'll have to secure this method since is a very sensitive one.
In the case that each tenant is a separate client you can just use keycloak's "Scope" mapping at each client. Just turn off Full Scope Allowed and your tokens will only contain the user's roles for that specific client (tenant).
"Scope Mappings" is a a non intuitive way of saying "Define what roles should go into the access token" :-)
When turned off the UI changes and you even can configure what other roles of other clients should additionally go into the access token.
Just to give some closure to this question:
No, there doesn't seem to be any elegant or intended way to force a manual token renewal by means of using springboot/spring security keycloak connector.
The Javascript connector can do this trivially like this:
// for creating your keycloak connector
var keycloak = Keycloak({
url: 'http://localhost:8080/auth',
realm: '[YOUR_REALM]',
clientId: '[YOUR_CLIENT]'
});
// for login in (change scopes list to change access capabilities)
var options = {
scope: [EMPTY_STRING_SEPARATED_LIST_OF_SCOPES] // <-- here specify valid scopes
};
keycloak.login(options); // <-- receive a new token with correctly processed scopes
Given how easy it is to do this with the Keycloak client JS adapter, and how obscure it is to do this with the springboot/spring security adapter, it follows following:
Security design seems intended to have 2 (Keycloak security) layers; the first is a front-facing public client (usually password protected), and the 2nd layer is composed of several bearer-only services which would ussually only accept acces-tokens. If for those bearer-only services you want to implement finner grained control via scopes, you achieve that trivially by using a javascript based Keycloak client (other connectors as explained won't deal nicely with the header modification necessary to deal with OAuth2 scopes).
I created a spring boot service that is secured by the spring-security-keycloak-adapter. As the service already knows about the (keycloak) identity provider, I don't see any point in sending the issuerUrl and clientId to the mobile client to login directly into keycloak. Instead, I want to simply call the loginurl of the service in a webview on the client. In my understanding spring should redirect to keycloak and in the end return the token.
Unfortunately all flutter packages require the clientId and issuerUrl for the oauth process
I alread tried the openid_client package for flutter
As your can see in the following code example from the official repository it requires the clientId and issuerUrl
// import the io version
import 'package:openid_client/openid_client_io.dart';
authenticate(Uri uri, String clientId, List<String> scopes) async {
// create the client
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
// create an authenticator
var authenticator = new Authenticator(client,
scopes: scopes,
port: 4000);
// starts the authentication
var c = await authenticator.authorize(); // this will open a browser
// return the user info
return await c.getUserInfo();
}
Full disclosure: I didn't write Flutter, but I did write some of the related client code for Spring Security.
Why issuerUri? The reason for this is likely for OIDC Discovery. You can use the issuer to infer the other authorization server endpoints. This cuts down on configuration for you: You don't need to specify the token endpoint, the authorization endpoint, and on and on. If you supply only the issuer, then flutter figures out the rest.
Note that with Spring Security, this is just one configuration option among multiple, but something needs to be specified either way so the app knows where to go. I can't speak for flutter, but it may just be a matter of time before it supports more configuration modes.
Why clientId? This is a security measure and is required by the specification. If someone is calling my API, I want to know who it is. Additionally, authorization servers will use this client_id to do things like make sure that the redirect_uri in the /authorize request matches what is configured for that client_id.
I've been following Caroline's blog to setup a multi-user composer rest server. So, I have two servers viz. Admin Server and the User Server.
As mentioned in the tutorial I:
Started the Admin Server with no authentication and single user mode. I started this server with Admin's card.
Started the User Server with passport JWT authentication in multi-user mode. I started this server with Admin's card as well.
Created a User participant and generated a card for user from the admin server.
In this step I'm trying to exchange the JWT Token with the User Server(#2) and I'm able to get the token as well.
Ping user server with JWT Token. This results in "Error: Authorization Required".
I've followed Chris Ganga's blog for implementing JWT. My COMPOSER_PROVIDERS is:
COMPOSER_PROVIDERS='{
"jwt": {
"provider": "jwt",
"module": "/home/composer/node_modules/custom-jwt.js",
"secretOrKey": "somesecretkey",
"authScheme": "saml",
"successRedirect": "/",
"failureRedirect":"/"
}
}'
I'm exchanging JWT token for the first time from a Java service. To create a bearer token, I've written the following code:
public static String getBearerToken(String username, String id) throws UnsupportedEncodingException {
return Jwts.builder()
.claim("timestamp", System.currentTimeMillis())
.claim("username", username)
.claim("id", id)
.signWith(
SignatureAlgorithm.HS256,
"somesecretkey".getBytes("UTF-8")
).compact();
}
With this, I'm able to get the token. Next, I use this token to import the card into the wallet on User server:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set("X-Access-Token",getAccess_token(participantEmail));
headers.set("x-api-key", userServerKey);
LinkedMultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("card", new FileSystemResource(card));
params.add("name", participantEmail);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(CARD_IMPORT_URL);
ResponseEntity<String> responseEntity = restTemplate.exchange(builder.build().encode().toUri(),
HttpMethod.POST, requestEntity, String.class);
However, this results in:
Unhandled error for request POST /api/Wallet/import: Error: Authorization Required
Finding 1
Generally, when we exchange the JWT for the first time using auth bearer, a db named "test" is created in mongo. This db contains three collections: accessToken, user and userIdentity. However, in my case when I exchange the token, no db is created in mongo. Any suggestions on how I can debug this?
Note:
This whole setup was working perfectly fine until I decided to prune and restart the network from scratch.
The issue here was I was using the same name for mongodb container across all the hosts. And since all the containers connect to the swarm network, there were 8 mongodb containers with the same name which is quite a silly mistake. It caused an issue when the docker container of composer-rest-server tried to connect to the mongodb container. This connection is defined in COMPOSER_DATASOURCES variable. Using the different name for each of the mongo containers across all the hosts solved this issue.
In my Spring MVC application I am using spring security. It works fine so far, but I have to admit, I do not understand all the details.
In the same application the user can call some controller functions by rest api. When the user (lets say Tom) does this, his authentication is lost. Instead the controller is called by user anonymous.
I tracked down that "user switch" to the code below. Variable restCall contains an url to my application. That call would work for user Tom, if he would place it in the browser directly. Using the restcall, the user changes to anyonymous.
1) Can I prevent that, when the calling User (Tom) was already logged in?
2) As those services should be called by a user that is not already browsing the web interface of the application, I would have to find a way to login by the rest call itself.
private void callWebservice(HttpServletRequest req, String restCall) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response
= restTemplate.getForEntity(restCall, String.class);
logger.debug(response.toString());
//assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
}
You need, for example, a JSON Web Token (JWT) Authentication.
Steps are the following:
1) Client does an authentication, a post request with username and password
2) If authentication is successful, server returns a token in which is coded the user
3) In your next call, GET or POST, you add JWT to your header, this way the server will know who you are, because server can decode the token, and can grant also the right authorities and functionalities