i am trying to sign a http request to aws api gateway in android using okhttp. i have more or less used the code in this stackoverflow question stackoverflow question
i use CognitoCachingCredentialsProvider() to get a credentialsProvider object. i then use getCredentials() to get the credentials. i then use the following: credentials.getAWSAccessKeyId(), credentials.getAWSSecretKey() and credentials.getSessionToken() to get the necessary keys and token. i use them in postman and am able to successfully execute the api gateway.
the request fails in android using okhttp, returning a code 403 with the message "Missing Authentication Token".
this is how i prepare the request: i build a DefaultRequest object, setting the endpoint and httpmethod. i then use AWS4Signer to sign the request, passing the credentials object as the signer.sign(defaultRequest, credentials) parameter.
i get a map of headers by calling getHeaders() on the defaultRequest. i create two lists, one called key for the key and one called value for the value. i then loop through the map, loading the keys and corresponding values into the two lists.
i then build my okhttp request as follows:
Request request = new Request.Builder()
.url(my ApiEndPoint)
.addHeader(key.get(0), value.get(0))
.addHeader(key.get(1), value.get(1))
.addHeader(key.get(2), value.get(2))
.addHeader(key.get(3), value.get(3))
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.post(body)
.build();
i notice the following:
in the headers map, key x-amz-security-token has a value ....ending in hKADF87VZ44w9IvZ1gU=
printing out the okhttp request, the key x-amz-security-token has a value .... ending in hKADF87VZ44w9IvZ1gU\u003d
the = is replaced by \u003d, could this be the problem? if so, how to prevent this?
otherwise, any help in solving this problem will be greatly appreciated.
thanks
managed to solve the problem. seems that assigning the headers to the OkHttp request was the problem. so here's my code:
i first get AWSSessionCredentials credentials. then:
AmazonWebServiceRequest amazonWebServiceRequest = new AmazonWebServiceRequest() {
};
String API_GATEWAY_SERVICE_NAME = "execute-api";
com.amazonaws.Request requestAws = new DefaultRequest(amazonWebServiceRequest, API_GATEWAY_SERVICE_NAME);
you can use either the service endpoint:
URI uri = URI.create("https://apigateway.eu-west-1.amazonaws.com");
or your api url (the invoke url for api as per Api Gateway console Stages option (The deployed api)):
String invokeUrl = "https://xxxx.execute-api.eu-west-1.amazonaws.com/yyy/zzzzz";
// using the invoke url
URI uri = URI.create(invokeUrl);
requestAws.setEndpoint(uri);
requestAws.setResourcePath(invokeUrl);
requestAws.setHttpMethod(HttpMethodName.POST);
now sign the request
AWS4Signer signer = new AWS4Signer();
signer.setServiceName(API_GATEWAY_SERVICE_NAME);
signer.setRegionName(Region.getRegion(Regions.EU_WEST_1).getName());
signer.sign(requestAws, credentials);
get the headers
// get map of headers
Map<String, String> headers = requestAws.getHeaders();
// create objects for the headers to add manually in OkHttp request builder
String x_date = null;
String x_token = null;
String authorization = null;
//get and assign values
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (entry.getKey().equals("x-amz-security-token")) {
x_token = entry.getValue();
}
if (entry.getKey().equals("X-Amz-Date")) {
x_date = entry.getValue();
}
if (entry.getKey().equals("Authorization")) {
authorization = entry.getValue();
}
}
build the OkHttp request:
Request request = new Request.Builder()
.url(invokeUrl)
.addHeader("Content-Type", "application/json")
.addHeader("X-Amz-Date", x_date)
.addHeader("x-amz-security-token", x_token)
.addHeader("Authorization", authorization)
.post(body)
.build();
now make your OkHttp call.
hope this is helpful to someone.
Related
I am working on an Android app in which a log in post request is made to a webservice. The request returns a cookie which expires in 20 minutes.
Using okhttp3 and this PersistentCookieStore library, I got the cookie to be stored and subsequently added it as request header to access authentication-required get requests (e.g. personal information that are non-public).
The code goes this way,
CookieJar myCookieJar = new PersistentCookieJar(new SetCookieCache(),
new SharedPrefsCookiePersistor(this));
OkHttpClient client = new OkHttpClient.Builder().cookieJar(HttpRequests.cookieJar).build();
I then call a method like this inside an (after I have gone through another log in Async task to get the cookie) Async task to perform a get request that requires authentication,
public static String PostReq(String url, String json) {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.addHeader("Cookie", "key=value")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
catch(Exception e){
}
}
The .addHeader("Cookie", "key=value") adds the cookie to the header to tell the webservice that I am authenticated.
Here comes my difficulty. Since the cookie expires after 20 minutes, I would like to be able to access the cookie itself to check for the expiration time and possibly redirect the user to the log in activity by calling the method,
myCookie.expiresAt()
and comparing it to
System.currentTimeMillis()
I tried to look at the PersistentCookieStore codes and found that it uses a SharedPreference with the key "CookiePersistence". I looked inside this file while my emulator was running the app and found it to be empty however.
How would I be able to access this cookie that I have obtained? Much thanks for any advice to be given.
OK, this is old, but I was facing the same problem, and here is how I fixed it.
Hold a reference to your SetCookieCache used to instantiate your CookieJar:
SetCookieCache cookieCache = new SetCookieCache();
CookieJar myCookieJar = new PersistentCookieJar(
cookieCache,
new SharedPrefsCookiePersistor(this)
);
Then use this to find your cookie and check it:
for (Cookie cookie : cookieCache) {
if (cookie.name().equals("cookie_name") && cookie.persistent()) {
//cookie is still good
break;
}
}
Or use cookie.expiresAt() to do your thing.
I am attempting to call a put method on my server using OkHttp from an Android application.
This is the api method signature:
public void Put(int userId, string regId)
{
}
This is the Android code to call the above method:
private boolean SendGCMRegIdToServer(String registrationId, Integer userId) throws IOException {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host(serverApiHost)
.addPathSegment("AppDashboard")
.addPathSegment("api")
.addPathSegment("GCM/")
.build();
MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
String json = "{'userId':" + userId + ","
+ "'regId':'" + registrationId + "'"
+ "}";
RequestBody requestBody = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.put(requestBody)
.build();
//this should post the data to my server
Response response = client.newCall(request).execute();
if(response.code() == 400)
return false;
return true;
}
Now the problem is I am getting the error code 405 in the response saying Method not allowed, but I cannot see where the problem is because I can successfully call the method using Postman on the server itself as below:
http://localhost/AppDashboard/api/GCM?userId=5®Id=123
I'm thinking it may have something to do with an integer or string being passed incorrectly in the JSON string, but cannot see why this isn't working.
i had the same problem and server was returning 405 . after some search i realized that is a configuration problem on IIS that does not let put requests. so there is no problem in android code and you should config your server to let this kind of requests.
see this , this and this
Ok thanks for replies guys but seems I was getting a little confused between the two methods I was using to pass the params to my API.
Here's what I did:
changed the signature of the method to post with a param [FromBody] as a Model (only supports one paramater)...
public void Post([FromBody]UserGcmRegIdModel model)
{
}
I was then able to change my method call to the following using a nicer JSONBuilder and using .post in the request builder rather than .put
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("UserId", userId);
jsonObject.put("RegId", registrationId);
} catch (JSONException e) {
e.printStackTrace();
}
String json = jsonObject.toString();
RequestBody requestBody = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
I still don't know if there is a problem with put() methods on IIS but using a post in my case was absolutely fine so I'm going with that...
I see two different approaches in your REST api calls. In the one of OkHttp you send a PUT method with a JSON object serialized, and in POSTMAN you send a PUT (although I guess you do a GET) request with the parameters within the URL, I mean not in JSON body structure.
Anyway, HTTP 405 is telling you that your backend does not support the PUT method, and probably it's expecting a POST method with the "X-HTTP-Method-Override:PUT" HTTP header since POST is more standard method in REST than PUT.
What would I do is check your POSTMAN request carefully and adjust the one of Android to be the same method, parameters and headers, not more.
Answer Update (as question has been updated)
Of course there is a problem with that verb, as I said above IIS handles only the standard methods and PUT is not one of those. You have three choices:
Change your PUT to POST.
Use POST with X-HTTP-Method-Override to PUT. (reference)
Modify IIS config to support non standard REST methods. I
personally wouldn't suggest the 3rd one, since it's attached to the
backend config (e.g. imagine you change IIS to NancyFX).
I am using OkHttp to make a POST request to the Bitbucket API to create a new issue. Their documentation says that a private repo or a private issue tracker require authentication to create new issues with this API. Both my repo and my issue tracker are public so there shouldn't be any issues with that. However when I make the call to create a new issue I get a 401 unauthorized response. Here is the code I am using to make the call:
FormEncodingBuilder builder = new FormEncodingBuilder();
String t = "{" +
"\"status\": \"new\"," +
"\"priority\": \"trivial\"," +
"\"title\": \"This is a title\"," +
"\"content\": \"This is the content\"," +
"\"is_spam\": false\n" +
"}";
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), t);
String url = "https://bitbucket.org/api/1.0/repositories/userName/repoName/issues"
Request request = new Request.Builder().url(url)
.post(requestBody)
.build();
Response response = new OkHttpClient().newCall(request).execute();
The Question:
Can anyone tell me why I am getting an unauthorized response back?
UPDATE
Per Jim Redmond I have changed my request to authenticate first. I am now making a POST to https://bitbucket.org/site/oauth2/authorize?client_id=myConsumerKey&response_type=token and I get response 200 OK but I do not see a token in the headers... Any idea why I'm not getting a token in response?
As the documentation also says, authentication is required for this method. It doesn't matter if your repo or issue tracker are public; there still needs to be a "reported_by" user.
I am using an API that uses an authorization scheme that requires a special "X-Authorization" header to be set to authenticate the request. For example, this Retrofit setup works perfectly for the user whose auth token is abc123:
#Headers("X-Authorization: abc123")
#GET("/posts")
Observable<List<Post>> get_posts();
I cache the user's X-Authorization token, so I have access to that, however, I can't just drop it in the #Headers declaration.
#Headers("X-Authorization: " + token)
#GET("/posts")
Observable<List<Post>> get_posts();
I get a compile error here: Error:(41, 34) error: element value must be a constant expression
Any ideas on how I could get around this?
Since Retrofit 2.0 you have two options
1) Using OkHttp 2.2+ use Interceptor
At the Http level, you have more control over the request, so you could do things like applying headers only to a specific request made to a specific endpoint, and so on.
public class MyOkHttpInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (!"/posts".contains(originalRequest.url()) ) {
return chain.proceed(originalRequest);
}
String token = // get token logic
Request newRequest = originalRequest.newBuilder()
.header("X-Authorization", token)
.build();
return chain.proceed(newRequest);
}
[...]
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.networkInterceptors().add(new MyOkHttpInterceptor());
OkClient okClient = new OkClient(okHttpClient);
YourApi api = new RestAdapter.Builder()
.setEndpoint(url)
.setClient(okClient)
.build()
.create(YourApi.class);
Edit:
Adding #JakeWarthon comment as another option as is also valid.
2) Put #Header on a method parameter and pass it as a value when invoking.
From the docs:
// Replaces the header with the the value of its target.
#GET("/")
void foo(#Header("Accept-Language") String lang, Callback<Response> cb);
Header parameters may be null which will omit them from the request. Passing a List or array will result in a header for each non-null item.
Note: Headers do not overwrite each other. All headers with the same name will be included in the request.
EDIT: This option should not be considered as Retrofit 2.* dropped support for interceptors.
3) User retrofit RequestInterceptor
From the docs:
Intercept every request before it is executed in order to add additional data.
You could do something like
public class MyRetrofitInterceptor implements RequestInterceptor {
#Override
public void intercept(RequestFacade req) {
String token = // get token logic
if (token != null) {
req.addHeader("X-Authorization", token);
}
}
[...]
YourApi api = new RestAdapter.Builder()
.setEndpoint(url)
.setRequestInterceptor(new MyRetrofitInterceptor())
.build()
.create(YourApi.class);
The "problem" with this approach is that the interceptor will get executed on all the endpoints, as it's set at the RestAdapter level, and not per endpoint. Also, the RequestFacade doesn't expose much information about the request, so no chance to add much logic around it.
Passing header in parameter would be helpful. Look to the following code;
#GET("/posts")
Observable<JsonElement> getDataFromService(
#HeaderMap Map<String, String> headers,
#QueryMap HashMap<String, Object> queryParams
);
hashMap1.put("Authorization", token);
return ApiService.getAPI_test().getDataFromService(hashMap1, url, hashMap)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
Update:
More better would be
#GET("/posts")
Observable<JsonElement> getDataFromService(
#Header("Authorization") token: String = "Bearer " + PreferenceUtils.getToken(),
#QueryMap HashMap<String, Object> queryParams
);
Dynamic Header In Retrofit 2
I have struggled too much to add Dynamic Header In Retrofit 2.
I have gone through so many blogs and StackOver flow. Everyone has shown example with Interceptor.
And it’s not a wise thing ,just for one API call we need to do that much work.
You just have to add #HeaderMap as argument of fun. I have done in very simple way :-
In Kotlin
val headers = HashMap<String, String>()
headers["KEY_AUTHORIZATION"] = "paste AUTHORIZATION value here"
headers["KEY_TOKEN"] = "paste TOKEN value here"
val jsonObject= JsonObject()
I am passing here header and other data also
Calling of fun:-
postEvent(headers,jsonObject)
API Declaration
#POST("/v1/post_data")
fun postEvent(#HeaderMap headers: Map<String, String>, #Body jsonObject: JsonObject): Call<JsonObject>
API Declaration with RxAndroid
#POST("/v1/post_data")
fun postEvent(#HeaderMap headers: Map<String, String>, #Body jsonObject: JsonObject): Single<JsonObject>
2nd argument here i have JsonObject. You can replace with anything whatever you need to pass or you can remove it also.
In Java
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("KEY_AUTHORIZATION","paste AUTHORIZATION value here");
headers.put("KEY_TOKEN", "paste TOKEN value here");
JsonObject jsonObject= new JsonObject();
I am passing here header and other data also
Calling of fun:-
postEvent(headers,jsonObject);
API Declaration
#POST("/v1/post_data")
Call<JsonObject> postEvent(#HeaderMap Map<String, String> headers, #Body JsonObject jsonObject);
API Declaration with RxAndroid
#POST("/v1/post_data")
Single<JsonObject> postEvent(#HeaderMap Map<String, String> headers, #Body JsonObject jsonObject);
2nd argument here i have JsonObject. You can replace with anything whatever you need to pass or you can remove it also.
A request Header can be updated dynamically using the #Header annotation. A corresponding parameter must be provided to the #Header. If the value is null, the header will be omitted. Otherwise, toString will be called on the value, and the result used.
#GET("user")
Call<User> getUser(#Header("Authorization") String authorization)
When the last part of this answer
How to dynamically set headers in Retrofit (Android)
did not work for me (halfway of the project), I improved it:-
public class MyRetrofitInterceptor implements RequestInterceptor {
// volatile variable
public static String token = null; //change at start of app
#Override
public void intercept(RequestFacade req) {
// change token from outside the class.
if (token != null) {
req.addHeader("X-Authorization", token);
}
}
It worked as soon as the token was updated from the response from the server API.
I think it worked as the string variable 'token' was used as the reference to its value, in global terms (being public static).
I'm trying to send a post request using the google api client library but not able to succeed.
This is the snippet I'm using
UrlEncodedContent urlEncodedContent = new UrlEncodedContent(paramMap); //paramMap contains email and password keypairs
HttpRequest request = httpRequestFactory.buildPostRequest(new GenericUrl(Constants.PHP_SERVICE_BASE_PATH + mPath) , urlEncodedContent);
String response = request.execute().parseAsString();
I do not get the expected response. I think it is because the post parameters i.e email and password are not being sent in the correct format. I need to send them in JSON.
NOTE : I'm not using the library for a google web service.
I'm using a Map as input of the JSON. The map is input for the JsonHttpContent used by the post request.
Map<String, String> json = new HashMap<String, String>();
json.put("lat", Double.toString(location.getLatitude()));
json.put("lng", Double.toString(location.getLongitude()));
final HttpContent content = new JsonHttpContent(new JacksonFactory(), json);
final HttpRequest request = getHttpRequestFactory().buildPostRequest(new GenericUrl(url), content);
UrlEncodedContent is used for posting HTTP form content (Content-Type: application/x-www-form-urlencoded). If the Content-Type is application/json you should probably use
http://code.google.com/p/google-http-java-client/source/browse/google-http-client/src/main/java/com/google/api/client/http/json/JsonHttpContent.java