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());
Related
I am trying to setup code in such as way that executes a POST request in my step definition class, and the POST request includes authorization (bearer token), content type, and Cookies in headers, as well as a request body in json format.
I have the bearer token process setup and am using the bearerToken for the authorization as header, content type is application/json, and the request body has the below structure:
I am unsure how to proceed in this way using Rest Assured. Below is a given method that sets the base uri and the bearer token based on environment, and based on that the bearer token will be used accordingly for authorization as header when executing the POST request:
#Given("Request the environment for the Master API {string}")
public void getMasterAPIBaseUrl(String region) throws JSONException {
if (region.equalsIgnoreCase("DEV")) {
bearerToken = accTokenSetup.accessTokenSetup(region);
RestAssured.baseURI = urlProps.getProperty("devMasterAPIUrl");
}
else if (region.equalsIgnoreCase("STG")) {
bearerToken = accTokenSetup.accessTokenSetup(region);
RestAssured.baseURI = urlProps.getProperty("stgMasterAPIUrl");
}
Reporter.log("Getting master api base url for the environment which is " + RestAssured.baseURI, true);
}
Now I need an #When step that will execute the mentioned POST request with the request body and the headers (authorization, content-type, and Cookies). I know what is required would be the endpoint, headers, request body, etc. but unsure of how to proceed with it. Any help would be appreciated!
I want to send a post request which has a bearer authorization required in java. The body of the post request takes parameters of in the following json format in postman:
{
"name": "project-002",
"description": "Test number 2",
"users": [
"ppallavalli#umass.edu"
]
}
I want to send a post request in the following way by which i executed my get request which needed authorization because authorization easier this way.
HttpUriRequest httpGet = RequestBuilder.get().setUri("https://sandbox.predera.com/aiq/api/projects/a-443018111").setHeader(HttpHeaders.AUTHORIZATION,authHeader).build();
In specific i want to send a post request using HttpUriRequest and RequestBuilder.post because authorization is easier in this case especially because the authorization required in my case is a Bearer token. Also, in the above request execution, auth header is string i have already initialized to a Bearer authorization token
You can use RequestBuilder.post() to build a POST request with a StringEntity to pass your JSON String. Rest of the headers including Authorization etc. should remain same.
String url = "https://sandbox.predera.com/aiq/api/projects/a-443018111";
String jsonString = "{...}";
HttpUriRequest httpPost = RequestBuilder.post(url).setHeader(HttpHeaders.CONTENT_TYPE, "application/json").addHeader(HttpHeaders.AUTHORIZATION, authHeader).setEntity(new StringEntity(jsonString)).build();
...
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.
I have a problem with sending API request via postman or Java lib "io.restassured".
When I do the same action on UI the request returns correct response, but when I try the same thing via postman or java code I get:
401 Bad request Your browser sent an invalid
request.
The java code
public static void main(String[] args) {
String requestUrl = "exampleBaseUrl/app/reports/api/rest/v1/graphs?context=shipper&reports_type=freights";
Response response = RestAssured.given().relaxedHTTPSValidation().header("x-csrf-token", "18ea65e740eb0ddddadf0ef435d92564").
when().
get(requestUrl);
}
I assume something is wrong with the authentication, because in dev tools i can see a Get request for CSRF_token, and it looks like this:
the endpoint for the token:
/login?get_csrf_token
and for this request I get following response:
{"csrf_token":"18ea65e740eb0ddddadf0ef435d92564"}
I am not sure how to solve this, I have also tried to get the token via java code by sending a get request to the token's endpoint /login?get_csrf_token
and this one gets my a HTML response with empty username and password input.
Error 401 means your request isn't authorized.
For authorization, usually while logging in you are given a token, which you will have to keep in your cache/local-memory and whenever you communicate with the server you have to add that in your request header (for your own introduction to the server)
It seems like in your case you can get a token from /login?get_csrf_token after logging in. Note that you don't need authorization for a login service.
Now, after getting token from the server, how to add it as a request header? See REST Assured Documentation