I am using authentication of users in Java 8 against Keycloak, with the Keycloak adapter API for Java.
In this case, the class KeycloakBuilder (keycloak-admin-client-6.0.0.jar) builds a Keycloak instance to perform authentication operations.
how can I request an offline token rather than a normal Bearer token using this API?
Have not found parameter or way to request it. I need tokens with 1 month expiration time, which cannot get unless change the "SSO Session Max" field, but I don´t want this to affect other Clients or users in the same Realm / client.
I am not sure if there are any specialties with the Keycloak Java adapter but I already implemented this with other clients. On the Authorization server side, you need to add a role offline_access to the users, which are allowed to request an offline session (this can be done explicitly or as a default role mapping). On the client side, you have to add another scope offline_access to the auth request. This can also be done by default (see default scopes). Please refer to the official Keycloak documentation about Offline Sessions for further details.
I post a possible solution using keycloak-authz-client library instead.
As stated by #Philipp , it is also necessary that the user you log in with has the role offline_access.
public String login(String username, String password) {
String authServerUrl = "http://localhost:18080/auth"; // Your keycloak auth entpoint
String realm = "realm"; // Realm
String clientId = "client"; // Client
Map<String, Object> clientCredentials = new LinkedHashMap<String, Object>();
clientCredentials.put("secret", "clientSecret"); // Client secret (Access Type: Confidential)
Configuration configuration = new Configuration(
authServerUrl,
realm,
clientId,
clientCredentials,
null
);
AuthzClient authzClient = AuthzClient.create(configuration);
AuthorizationRequest request = new AuthorizationRequest();
request.setScope("offline_access");
AuthorizationResponse response = authzClient.authorization(username, password).authorize(request);
return response.getRefreshToken(); // response.getToken() returns the bearer token
}
Related
Why I am not able to call graphClient more then once?
Code:
public static void initializeGraphAuth(String authorizationCode) {
List<String> scopes = new ArrayList<>();
scopes.add("https://graph.microsoft.com/mail.read");
// Create the auth provider
final AuthorizationCodeCredential authCodeCredential = new AuthorizationCodeCredentialBuilder().clientId(AzureConstants.CLIENT_ID).clientSecret(AzureConstants.CLIENT_SECRET).authorizationCode(authorizationCode) .redirectUrl(AzureConstants.REDIRECT_URI).build();
authProvider = new TokenCredentialAuthProvider(scopes, authCodeCredential);
// Create default logger to only log errors
DefaultLogger logger = new DefaultLogger();
logger.setLoggingLevel(LoggerLevel.ERROR);
// Build a Graph client
graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).logger(logger).buildClient();
}
public static User getUserDetails() {
return graphClient.me().buildRequest().get();
}
public static List<Group> getUserGroups() {
GroupCollectionPage groups = graphClient.me().transitiveMemberOfAsGroup().buildRequest().get();
return groups.getCurrentPage();
}
In main app I am calling getUserDetails() and getUserGroups() methods to get users details and group details respectively. Able to get User details but not group details below is the error
com.microsoft.aad.msal4j.MsalInteractionRequiredException: AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token.
Trace ID: 48d1fee1-cb8b-48c6-a7ec-91e2b2057500
Correlation ID: c58388ec-417c-4398-82ee-68910568f4df
If i call only one method either getUserDetails or getUserGroups its is working fine, but when i call both methods in code it is giving error
How can i use graphClient object to get user and group details both??
Thanks for your help
When your application uses authorization codes to obtain tokens, this behavior is to be expected.
In this situation, refresh tokens can be used to obtain extra tokens for other resources.
Refresh tokens can be used several times across multiple resources, whereas authorization codes can only be used once.
When Credential uses a refresh token, it also updates the access token when the access token expires.
You can receive a new access token using a refresh token by using the Google OAuth2 client library.
For more information on this, you can refer OAuth 2.0 and the Google OAuth Client Library for Java
REFERENCES:
OAuth2 Authorization code was already redeemed - Microsoft Q&A
How to get an access token using a refresh token in Java? - Stack Overflow
Technical Details:
Keycloak Version: 12.0.2
Java Version: 1.8
Java Admin Client: 12.0.2
Keycloak Spring boot starter
Description
I have a Spring Boot application where internal staff can create and modify Keycloak users, using its official java dependency. This application should also contain an impersonation function. I tried impersonating with the Java Admin Client, the impersonate function returns a map, which contains the Boolean “sameRealm” and the redirect URL to the account page of Keycloak. With these 2 values I can’t access the session of the impersonated User nor can I really use these attributes for something else. Afterwards I tried the impersonate function of the Keycloak Rest API, the API returned some Cookies in the header unfortunately I can’t seem to figure out how to use these cookies, but I tried creating these cookies and set them but unfortunately it didn’t work. Lastly, I tried a token exchange to receive a valid access token and fortunately the function works.
token-exchange function
Keycloak keycloakService = KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.clientId(clientId)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientSecret(clientSecret)
.build();
BasicCookieStore cookieStore = new BasicCookieStore();
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
HttpUriRequest reqBuild = RequestBuilder.post()
.setUri(serverUrl + "/realms/intern/protocol/openid-connect/token")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addParameter("client_id", "example")
.addParameter("client_secret", "example") //
.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.addParameter("subject_token", keycloakService.tokenManager().getAccessTokenString())
.addParameter("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
.addParameter("requested_subject", userId)
.addParameter("audience", "target-client")
.build();
HttpResponse res = httpClient.execute(reqBuild);
String resBody = EntityUtils.toString(res.getEntity());
System.out.println(resBody);
With the above code I receive a valid access-token from Keycloak. But unfortunately, I don’t know how I can use this token to complete the Impersonation process.
The setup is as following: id.example.com provides the Spring Boot Application, sso.id.example.com runs KeyCloak instance. For my understanding, I should create a cookie for sso.id.example.com
If someone has another solution on how I can Impersonate a user and then acquire the associated session. I would appreciate the help.
Your resBody.content is of type AccessTokenResponse (assuming you got a 200 response) which has an token property. The token should be the auth_token for the user you want to impersonate. As a sanity check you can use https://jwt.io/ to debug the jwt token returned to make sure it is for the correct user.
You can use this token for subsequent calls as the person you are impersonating.
I don't believe you need to set a cookie store, i am certain you can remove this and it should continue to work.
import org.keycloak.admin.client.Keycloak
...
val keycloakService = KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.clientId(clientId)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientSecret(clientSecret)
.build();
val httpClient = HttpClientBuilder.create().build();
val reqBuild = RequestBuilder.post()
.setUri(serverUrl + "/realms/intern/protocol/openid-connect/token")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addParameter("client_id", "example")
.addParameter("client_secret", "example") //
.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.addParameter("subject_token", keycloakService.tokenManager().getAccessTokenString())
.addParameter("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
.addParameter("requested_subject", userId)
.addParameter("audience", "target-client")
.build();
val response = httpClient.execute(reqBuild)
val entity = if (response.statusLine.statusCode == 200) {
val mapper = ObjectMapper().registerModule(KotlinModule())
mapper.readValue(response.entity.content, AccessTokenResponse::class.java)
} else {
// handle error
}
// this should log you in as the impersonated user
val impersonated = Keycloak.getInstance(serverUrl, realm, clientId, entity.token).realm(realm)
I've been experimenting with Azure Active Directory access for Java using two sample projects:
1) https://github.com/AzureAD/azure-activedirectory-library-for-java which builds a stand-alone war using OAuth tokens for security, and
2) https://github.com/Microsoft/azure-spring-boot/tree/master/azure-spring-boot-samples/azure-active-directory-spring-boot-backend-sample for spring-boot embeded containers
I've come across quite a difference in the way the APIs can be used, that I can't understand.
In both cases, I get an OAuth token for AD by logging in with my Azure credentials.
In the Http response, I get an authorizationCode of the form:
AQABAAIAAAD.....
Then using the following URL as an authContext:
https://login.microsoftonline.com/{tenantId}
I get a AuthenticationResult by making the following call:
Future<AuthenticationResult> future = authContext.acquireTokenByAuthorizationCode(authorizationCode, redirectUri, credential, null);
in the Adal4j project (1), the AuthenticationResult's AccessToken is of the form:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6I...
Which I can use as a Bearer token in an HTTP call to retrieve the user's profile picture via https://graph.windows.net/myorganization/me/thumbnailPhoto?api-version=1.6
whereas in the SpringBoot AD example, the AccessToken returned from exactly the same call is of the form:
AQABAAAAAADXzZ3ifr-GRbDT....
and If I use that in exactly the same way to try to retrieve the user's profile pic, I get a 401 Unauthorized response
What's the reason for the difference in the form and use of these AccessTokens?
What's the reason for the difference in the form and use of these AccessTokens?
I assume that you got the access token is authorization_code not the bearer token.
As Rohit Saigal mentioned that you could use JWT.IO or JWT.MS to check that.
If we want to get the access token for Azure AD graph we could use the follow code to do that.
public String index(Model model, OAuth2AuthenticationToken authentication) {
...
DefaultOidcUser user = (DefaultOidcUser)authentication.getPrincipal();
String accessToken = user.getIdToken().getTokenValue();
...
}
Then we could use the access token to access the Azure AD graph api if you have assiged corrosponding permission.
I'm using Identity Brokering feature and external IDP. So, user logs in into external IDP UI, then KeyCloak broker client receives JWT token from external IDP and KeyCloak provides JWT with which we access the resources. I've set up Default Identitiy Provider feature, so external IDP login screen is displayed to the user on login. That means that users and their passwords are stored on external IDP.
The problem occurs when I need to log in using "Direct Access Grant" (Resource Owner Password grant) programatically in tests. As password is not stored on KeyCloak, I always get 401 Unauthorized error from KeyCloak on login. When I tried to change user password it started to work, so the problem is that user password is not provisioned on KeyCloak and using "Direct Access Grant" KeyCloak doesn't invoke external IDP on programatic login.
I use the following code to obtain access token, but get 401 error everytime I pass valid username/password.
org.keycloak.authorization.client.util.HttpResponseException: Unexpected response from server: 401 / Unauthorized
Direct access grant is enabled for that client.
public static String login(final Configuration configuration) {
final AuthzClient authzClient = AuthzClient.create(configuration);
final AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken(USERNAME, PASSWORD);
return accessTokenResponse.getToken();
}
Is there any way it can be fixed? For example to call identity broker on "Direct Access Grant", so that KeyCloak provides us it's valid token?
The problem was that KeyCloak has no information about passwords from initial identity provider. They have a token exchange feature which should be used for programmatic token exchange.
External Token to Interanal Token Exchange should be used to achieve it.
Here is an example code in Python which does it (just place correct values in placeholders):
def login():
idp_access_token = idp_login()
return keycloak_token_exchange(idp_access_token)
def idp_login():
login_data = {
"client_id": <IDP-CLIENT-ID>,
"client_secret": <IDP-CLIENT-SECRET>,
"grant_type": <IDP-PASSWORD-GRANT-TYPE>,
"username": <USERNAME>,
"password": <PASSWORD>,
"scope": "openid",
"realm": "Username-Password-Authentication"
}
login_headers = {
"Content-Type": "application/json"
}
token_response = requests.post(<IDP-URL>, headers=login_headers, data=json.dumps(login_data))
return parse_response(token_response)['access_token']
def keycloak_token_exchange(idp_access_token):
token_exchange_url = <KEYCLOAK-SERVER-URL> + '/realms/master/protocol/openid-connect/token'
data = {
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
'subject_token': idp_access_token,
'subject_issuer': <IDP-PROVIDER-ALIAS>,
'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'audience': <KEYCLOAK-CLIENT-ID>
}
response = requests.post(token_exchange_url, data=data,
auth=(<KEYCLOAK-CLIENT-ID>, <KEYCLOAK-CLIENT-SECRET>))
logger.info(response)
return parse_response(response)['access_token']
This example was very useful for me, i only want to add more info about the KEYCLOAK-CLIENT used for the token-exchange (for me authorization_client). I have KEYCLOAK as a broker for IDP ADFS.
First you need to enable the token-exchange feature adding 2 parameters in your keycloak startup command line (depending of how you do it)
-Dkeycloak.profile=preview
-Dkeycloak.profile.feature.token_exchange=enabled
The IDP (for me ADFS) tab Permissions with the token-exchange permission will be available.
Add a policy to the "token-exchange" provider permission, to the client KEYCLOAK-CLIENT
Add this previous policy to the "token-exchange" client permission
With POSTMAN you can test the authentication flow:
External IDP ADFS Login Username/password
Token Exchange
I have succeed using openID and OAuth separately, but I can't make them work together.
Am I doing something incorrect:
String userSuppliedString = "https://www.google.com/accounts/o8/id";
ConsumerManager manager = new ConsumerManager();
String returnToUrl = "http://example.com:8080/app-test-1.0-SNAPSHOT/GAuthorize";
List<DiscoveryInformation> discoveries = manager.discover(userSuppliedString);
DiscoveryInformation discovered = manager.associate(discoveries);
AuthRequest authReq = manager.authenticate(discovered, returnToUrl);
session.put("openID-discoveries", discovered);
FetchRequest fetch = FetchRequest.createFetchRequest();
fetch.addAttribute("email","http://schema.openid.net/contact/email",true);
fetch.addAttribute("oauth", "http://specs.openid.net/extensions/oauth/1.0",true);
fetch.addAttribute("consumer","example.com" ,true);
fetch.addAttribute("scope","http://www.google.com/calendar/feeds/" ,true);
authReq.addExtension(fetch);
destinationUrl = authReq.getDestinationUrl(true);
then destinationUrl is
https://www.google.com/accounts/o8/ud?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.return_to=http%3A%2F%2Fexample.com%3A8080%2FgoogleTest%2Fauthorize&openid.realm=http%3A%2F%2Fexample.com%3A8080%2FgoogleTest%2Fauthorize&openid.assoc_handle=AMlYA9WVkS_oVNWtczp3zr3sS8lxR4DlnDS0fe-zMIhmepQsByLqvGnc8qeJwypiRQAuQvdw&openid.mode=checkid_setup&openid.ns.ext1=http%3A%2F%2Fopenid.net%2Fsrv%2Fax%2F1.0&openid.ext1.mode=fetch_request&openid.ext1.type.email=http%3A%2F%2Fschema.openid.net%2Fcontact%2Femail&openid.ext1.type.oauth=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Foauth%2F1.0&openid.ext1.type.consumer=example.com&openid.ext1.type.scope=http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F&openid.ext1.required=email%2Coauth%2Cconsumer%2Cscope"
but in the response from google request_token is missing
http://example.com:8080/googleTest/authorize?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=id_res&openid.op_endpoint=https%3A%2F%2Fwww.google.com%2Faccounts%2Fo8%2Fud&openid.response_nonce=2011-11-29T17%3A38%3A39ZEU2iBVXr_zQG5Q&openid.return_to=http%3A%2F%2Fexample.com%3A8080%2FgoogleTest%2Fauthorize&openid.assoc_handle=AMlYA9WVkS_oVNWtczp3zr3sS8lxR4DlnDS0fe-zMIhmepQsByLqvGnc8qeJwypiRQAuQvdw&openid.signed=op_endpoint%2Cclaimed_id%2Cidentity%2Creturn_to%2Cresponse_nonce%2Cassoc_handle%2Cns.ext1%2Cext1.mode%2Cext1.type.email%2Cext1.value.email&openid.sig=5jUnS1jT16hIDCAjv%2BwAL1jopo6YHgfZ3nUUgFpeXlw%3D&openid.identity=https%3A%2F%2Fwww.google.com%2Faccounts%2Fo8%2Fid%3Fid%3DAItOawk8YPjBcnQrqXW8tzK3aFVop63E7q-JrCE&openid.claimed_id=https%3A%2F%2Fwww.google.com%2Faccounts%2Fo8%2Fid%3Fid%3DAItOawk8YPjBcnQrqXW8tzK3aFVop63E7q-JrCE&openid.ns.ext1=http%3A%2F%2Fopenid.net%2Fsrv%2Fax%2F1.0&openid.ext1.mode=fetch_response&openid.ext1.type.email=http%3A%2F%2Fschema.openid.net%2Fcontact%2Femail&openid.ext1.value.email=example%40gmail.com
why?
In the above code, you have added OAuth extension parameters with the Attribute Exchange extension parameters. But since OAuth and Attribute Exchange are different extensions, therefore you have to create a different extension message for OAuth parameters and then add it to Authentication request message.
But since there is no mechanism to add OAuth parameters to the Authentication message, therefore you'll have to create such a mechanism. You can get information about it in the following link
http://code.google.com/p/openid4java/wiki/ExtensionHowTo
You can then use the code provided in the following link to hard code this mechanism
http://code.google.com/p/openid4java/issues/detail?id=110&q=oauth