Due to the architecture of our product, the loadbalancer that I use is not able to manage correctly my servers. To solve this point, I have create a kind of manager which forward the call to the correct server by requesting it directly from the first server when it is needed. The issue is to do that I forward the user keycloak token string in the header of the second call. It works pretty well but after few minutes to token is invalidate. I tried to refresh it by many way without success. I have 2 constraints. I can't reask the credentials to the user, and I can't use the keycloak secret because I'm working in a multi tenant / multi realm app. Do you have any idea?
I'm assuming that you have just the access_token on login.
First you need to exchange that access token for a refresh token (and another access token), you do this by calling:
HTTP POST {{host}}/auth/realms/{{realm}}/protocol/openid-connect/token
HEADERS:
Content-Type: application/x-www-form-urlencoded
BODY:
grant_type: urn:ietf:params:oauth:grant-type:token-exchange
subject_token: the_user_access_token
client_id: your_client_id (in my case this is "public")
requested_token_type: urn:ietf:params:oauth:token-type:refresh_token
you will get back a response that looks like this:
{
"access_token": "access_token_value",
"expires_in": access_token_time_in_seconds,
"refresh_expires_in": refresh_token_time_in_seconds,
"refresh_token": "refresh_token_value",
"token_type": "bearer",
...
}
So with this you have what you need to actually refresh your token with the following request:
HTTP POST {{host}}/auth/realms/{{realm}}/protocol/openid-connect/token
HEADERS:
Content-Type: application/x-www-form-urlencoded
BODY:
grant_type: refresh_token
refresh_token: refresh_token_value
client_id: public
This will get you the following response:
{
"access_token": "access_token_value",
"expires_in": access_token_time_in_seconds,
"refresh_expires_in": refresh_token_time_in_seconds,
"refresh_token": "refresh_token_value",
"token_type": "bearer",
...
}
Now you can repeat step 3 and 4 up until the maximum session duration.
Related
I have a client app using jax-rs Client and making REST calls to bunch of services. One of those services uses bearer token authorization, and after first hard authentication (BASIC) lends a json with an access token, expiry delay, token type etc...
{
"access_token": "hashed_value",
"token_type": "bearer",
"expires_in": 1800 }
I wish to know what would be the best practices for managing this token for replay. Would it be via a global structure like a Map in memory that I would access at each request and update when token comes to expiration ? an other structure ?
To get a new token after expiration, would it be better to manage it requesting a new token based on a 401 status code ? or by requesting it automatically 1 minute or so before the 1800 seconds passes ?
I am using postman and I've tried updating a user's profile via http://localhost:8180/auth/admin/realms/demo/users/{userID} but I received a 401 response.
The procedure I used:
Requested and received admin token via http://localhost:8180/auth/realms/master/protocol/openid-connect/token
Added token to request headers the appropriate way i.e Authorization: Bearer {access_token}
Sent Put request with Json content type and the user's info as body via http://localhost:8180/auth/admin/realms/demo/users/{userID}.
Unfortunately, I've received back-to-back 401 responses.
First request:
-Body(x-www-form-urlencoded)
client_id : admin_cli
username: ...
password: ...
grant_type: password
client_secret: ...
-To http://localhost:8180/auth/realms/master/protocol/openid-connect/token
Second request:
-Header -> Authorization: Bearer ...
-Body(JSON)
"email": "d#gmail.com",
"firstName": "divad",
"lastName": "d"
-To http://localhost:8180/auth/admin/realms/demo/users/{userID}
In your first call, the david user has to be one with admin-alike privileges. Otherwise, one gets an authorized error response for the actions that the david user does not have the privileges to perform. Have a look at this SO thread to check how to assign admin-alike privileges to a user.
For now let us request a token on the behalf of the master admin user as follows:
from the body response extract the access_token.
For the second call first, copy and paste the access_token to the Authorization > Type Bearer Token:
On the second call, instead of
http://localhost:8180/auth/admin/realms/demo/users/{userID}
you need to replace the userID parameter with the actual userID of the user that you are updating. To get userID you can call the following endpoint:
GET <YOUR_KEYCLOAK_DOMAIN>/auth/admin/realms/<YOUR_REALM>/users/?username=<THE_USERNAME>
or you can copy and paste from the Keycloak Admin Console, under the tab users:
So in Postman would look like:
In my application, all I want to do is to get the user's Gmail. I don't use any google services. I want to add Google Sign In for Java front end and validate the user in Java backend.
I am following openid-connect document.
By opening the following link in the browser, I can get the code.
(GET) Request:
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?
response_type=code&
scope=openid&
redirect_uri=http://localhost:3000/auth/&
client_id=113291176157....
/auth endpoint gets the following:
{
code: '4/0AfDhmrjDTOo7zOrXxm98E...',
scope: 'openid',
authuser: '0',
prompt: 'none'
}
Now to get user details,
(POST) Request:
https://oauth2.googleapis.com/token
{
"code": "4/0AfDhmrjDTOo7zOrXxm98E...",
"client_id": "113291176157-aibhsqjf655ve...",
"client_secret": "lLjenLdeaJnd...",
"redirect_uri": "http://localhost:3000/auth",
"grant_type": "authorization_code"
}
But I get the following response:
{
"error": "redirect_uri_mismatch",
"error_description": "Bad Request"
}
I have added the redirect URL to credentials:
Right now, I'm hoping to open a browser window from Java front end for the user to login to Google. I will receive the code value, and it will get id_token and validate the user.
I have seen some javascript examples, where they get the id_token directly.
So I have multiple questions,
Why do I need to get the code first and request id_token later?
Why am I getting redirect_uri_mismatch error?
Authorization code exchange request looks like below,
URL: https://oauth2.googleapis.com/token
POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//oauth2.example.com/code&
grant_type=authorization_code
The request should be POST and data should be sent as urlencoded data, not in the request body.
More information about code exchange
We are using Spring Cloud Netflix Zull as Gateway Application for our backend services. Front end is Angular. But we are testing endpoints in Postman only(Front end is under development). We have one module called LoginServiceModule and another is ZullServerModule. The LoginSericeModuke take username and password from front end and make call to the following oauth/token endpoint by including required headers and body.
http://localhost:XXXX/login
and response is
{
"access_token": "XXXXXXXXX",
"token_type": "bearer",
"refresh_token": "XXXXXXXXX",
"expires_in": 3600,
"scope": "read write",
"jti": "XXXXXXXXXXX"
}
and the ZullServerModule contains ZullServerConfiguration, Authorisation Server Configuration and Resource Server Configuration and etc...
The LoginModule internally calls oauth/token end point like this.
ResponseEntity<String> loginResponse = restTemplate.exchange("http://localhost:XXXX/oauth/token", HttpMethod.POST, entity, String.class);
and the response is ..
{
"access_token": "XXXXXXXXX",
"token_type": "bearer",
"refresh_token": "XXXXXXXXX",
"expires_in": 3600,
"scope": "read write",
"jti": "XXXXXXXXXXX"
}
we extract the access_token from the response and call following endpoint...
http://localhost:XXXX/ProjectName/api/endpointname?access_token={access_token}.
But when the access_token expires, and when i access the above backend service url, its saying
{
"error": "invalid_token",
"error_description": "Access token expired:XXXXXXXXXXXXXXX(access_token)"
}
I know its expired and tried regenerating access_token with help of refresh_token in terminal like this
curl clientID:clientSecret#localhost:XXXX/oauth/token -d grant_type=refresh_token -drefresh_token={refresh_token}
But i need to include this in our code and don't know where to place it.
After searching on net, i came across about ZullFilter. I tried all pre, route and post filters. For each request they are all executed (i.e all long as access_token not expired), but when token expires and if i test endpoint, none of the filters are executed and i am getting error response
{
"error": "invalid_token",
"error_description": "Access token expired: XXXXXXXXXX"
}
I have placed sysouts in run method of every filters. I don't know much about filterOrder also.
#Override
public Object run() throws ZuulException {
System.out.println("pre filter...");
RequestContext context =
RequestContext.getCurrentContext();
HttpServletResponse response = context.getResponse();
System.out.println(response.getStatus());
return null;
}
I want to control access_token generation with help of refresh_token. How could i code that whenever the access_token expires and if i access resources after expire,then i get to know that token expired and re generate access_token and call the previous call with new access_token.
Typically it is the client that is responsible for maintaining its own token and refreshing it when it is about to expire. Moving this logic into your Zuul layer seems like a really bad idea. Think about the implementation for a second, how would it work?
Once a client's token has expired, it would be calling your endpoints with a perpetually expired token that Zuul would have to try and refresh with every request. This would add a lot of overhead for each API call. You could possibly introduce some kind of hack where you always pass back a new token in a response header or something... but at this point you'd be violating the authorization flow of OAuth2.
Question
When using the Google Plus Sign In Api with the Play Framework do you have to set headers in a different way? Is there something I am doing wrong here?
Background
I am using Play Framework(in Java) to use the Google Plus Sign in Api.
I am running into issues on the second leg of OAuth authentication, exchanging the Authorization Code for a Token.
Basic OAuth Flow
Pretty Picture
Redirect user to User login/Consent screen
This asks the user if they want to grant you application permission to the requested scopes
URL: https://accounts.google.com/o/oauth2/auth
Exchange Authorization Code for a Token
If the user gives your application permission then they will be redirected to a URL you specify, in that URL(As a GET param) will be an Authorization Code.
Your application can then use this Authoriztion Code to get a Token from the server
Your application does this by making a HTTP request to a endpoint on the Google Servers(Or whatever service you are using)
URL: https://accounts.google.com/o/oauth2/token
Use Token in API requests
The Issue
To Exchange the Authorization Code for a Token, with the Google Plus Sign In Api, you must make a POST request to https://accounts.google.com/o/oauth2/token with the following perimeters
{
"code": "Security Code Returned from Step 1",
"client_id": "Client Id that was given to you in GApi Console",
"client_secret": "Client Secret that was given to you in the GApi Console",
"redirect_uri": "Redirect Uri you specified in the GApi Console",
"grant_type": "authorization_code"
}
However when I make this request with all the correct parameters I get this error
{
"error" : "invalid_request",
"error_description" : "Required parameter is missing: grant_type"
}
From the Google Plus Sign in Api
To make HTTP requests in The Play Framework you use the WS Library. I make the request like this
public static F.Promise<Result> OAuthCallback(String state, String code){
/*
Note:
- The GoogleStrategy class is just a class that holds all my GApi credentials
- The parameters (String state, String code) are just GET params from Step 1, returned by the GApi
*/
//Make URL builder
WSRequestHolder requestHolder = WS.url(GoogleStrategy.getTokenUrl);
//Set headers
requestHolder.setHeader("code", code);
requestHolder.setHeader("client_id", GoogleStrategy.clientId);
requestHolder.setHeader("client_secret", GoogleStrategy.clientSecret);
requestHolder.setHeader("redirect_uri", GoogleStrategy.redirectUri);
requestHolder.setHeader("grant_type", GoogleStrategy.grantType);//GoogleStrategy.grantType = "authorization_code"
//Make HTTP request and tell program what to do once the HTTP request is finished
F.Promise<Result> getTokenPromise = requestHolder.post("").map(
new F.Function<WSResponse, Result>() {
public Result apply(WSResponse response){
return ok(response.asJson());//Returning result for debugging
}
}
);
return getTokenPromise;//Return promise, Play Framework will handle the Asynchronous stuff
}
As you can see, I set the header grant_type. Just to make sure setting headers was working I made a program that spits out the headers of a request in NodeJS(Source) and this was the result
{
"HEADERS": {
"host": "127.0.0.1:3000",
"code": "4/qazYoReIJZAYO9izlTjjJA.gihwUJ6zgoERgtL038sCVnsvSfAJkgI",
"grant_type": "authorization_code",
"client_secret": "XXXX-CENSORED FOR SECURITY PURPOSES-XXX",
"redirect_uri": "http://127.0.0.1:9000/api/users/auth/google/callback",
"client_id": "XXXX-CENSORED FOR SECURITY PURPOSES-XXX",
"content-type": "text/plain; charset=utf-8",
"connection": "keep-alive",
"accept": "*/*",
"user-agent": "NING/1.0",
"content-length": "14"
}
}
I think those are not to be sent as headers but as a body. In the link you provided there is an example:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2-login-demo.appspot.com/code&
grant_type=authorization_code
So pass them to your post call:
StringBuilder sb = new StringBuilder();
sb.append("code=").append(code)
.append("&client_id=").append(GoogleStrategy.clientId)
.append("&client_secret=").append( GoogleStrategy.clientSecret)
.append("&redirect_uri=").append(GoogleStrategy.redirectUri)
.append("&grant_type=").append(GoogleStrategy.grantType)
requestHolder.setContentType("application/x-www-form-urlencoded")
.post(sb.toString());