Having an issue using Cognito and API Gateway to access our REST API's
I can't for the life of me find a simple example of how to call a secured REST api on our AWS server.
I call
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
ProfileViewActivity.this, // get the context for the current activity
AMAZON_COGNITO_IDENTITY_POOL_ID, // your identity pool id
Regions.US_EAST_1 //Region
);
and then try to make a call to our server which is a part of that POOL. I keep getting back "Forbidden".
What should I do with credentialsProvider in the headers of the HttpURLConnection ?
UPDATED:
Ok using the APIGateWay the right way with the AWS Generated SDK however unsure how to change the generated file:
private class MyAsyncTask extends AsyncTask<String, Void, String> {
private TextView textView;
public MyAsyncTask() {
}
#Override
protected String doInBackground(String... strings) {
AWSCredentialsProvider credenetialsProvider = new CognitoCachingCredentialsProvider(
ProfileViewActivity.this, // activity context
AMAZON_COGNITO_IDENTITY_POOL_ID, // Cognito identity pool id
Regions.US_EAST_1 // region of Cognito identity pool
);
ApiClientFactory factory = new ApiClientFactory()
.credentialsProvider(credenetialsProvider)
.region("us-east-1")
.endpoint("https://myendpoint")
.apiKey("xxxxxxxxxxxxxxxxxxxxxxx");
// MyClient is the AWS Android SDK Generated class
final MyClient client = factory.build(MyClient.class);
client.feedGet();
String str = client.testGet().toString();
Log.d("###", "here after test" +client.testGet().toString());
return "DONE";
}
#Override
protected void onPostExecute(String temp) {
Log.d("####", "onPostExecute");
}
}
and then
private class MyAsyncTask extends AsyncTask<String, Void, String> {
private TextView textView;
public MyAsyncTask() {
}
#Override
protected String doInBackground(String... strings) {
AWSCredentialsProvider credenetialsProvider = new CognitoCachingCredentialsProvider(
ProfileViewActivity.this, // activity context
AMAZON_COGNITO_IDENTITY_POOL_ID, // Cognito identity pool id
Regions.US_EAST_1 // region of Cognito identity pool
);
ApiClientFactory factory = new ApiClientFactory()
.credentialsProvider(credenetialsProvider)
.region("us-east-1")
.endpoint("https://myendpoint")
.apiKey("xxxxxxxxxxxxxxxxxxxxxxx");
// MyClient is the AWS Android SDK Generated class
final MyClient client = factory.build(MyClient.class);
client.feedGet();
String str = client.testGet().toString();
Log.d("###", "here after test" +client.testGet().toString());
return "DONE";
}
#Override
protected void onPostExecute(String temp) {
Log.d("####", "onPostExecute");
}
}
========= MyClient.java - AWS Android SDK Generated File
#com.amazonaws.mobileconnectors.apigateway.annotation.Service(endpoint = https:myaws_server)
public interface MyClient {
#com.amazonaws.mobileconnectors.apigateway.annotation.Operation(path = \"/test\", method = "GET")
Empty testGet();
If you are using AWS API Gateway, you can use it independently without using AWS Cognito. AWS API Gateway is a http service just like a proxy to your server or a Gateway to your AWS Lambda Function.
aws api gateway
Related
I'm looking for some improvements and recommendations for the AmazonS3 presignedURL with Cognito. I've noticed that sometimes the expiration date expires sooner than set. This causes the error "Unexpected response code 400".
MainActivity.java
// Amazon S3
private AmazonS3 amazonS3;
public AmazonS3 getAmazonS3() {
return amazonS3;
}
public void setAmazonS3(AmazonS3 amazonS3) {
this.amazonS3 = amazonS3;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
// AWS
RunnableAWSS3 runnableAWSS3 = new RunnableAWSS3(activity);
Thread thread = new Thread(runnableAWSS3);
thread.start();
thread.join();
// Getting the JSON data stored in S3 bucket
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, runnableAWSS3.getPresignedURL(), null, response -> {
...
});
}
RunnableAWSS3.java
public class RunnableAWSS3 implements Runnable {
private final ActivityMain activityMain;
private URL presignedURL;
public RunnableAWSS3(#NonNull Activity activity) {
activityMain = (ActivityMain) activity;
}
#Override
public void run() {
if (activityMain.getAmazonS3() == null) {
// AWS
// Initialize the Amazon Cognito credentials provider
CognitoCachingCredentialsProvider cognitoCachingCredentialsProvider = new CognitoCachingCredentialsProvider(
activityMain,
"us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx",
Regions.US_EAST_1
);
activityMain.setAmazonS3(new AmazonS3Client(cognitoCachingCredentialsProvider, Region.getRegion(Regions.US_EAST_1)));
}
// Set the presigned URL to expire after twelve hours.
presignedURL = activityMain.getAmazonS3().generatePresignedUrl(
"xxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
new Date(System.currentTimeMillis() + 43200000));
}
public String getPresignedURL() {
return presignedURL.toString();
}
}
Sometimes I'm getting an error E/Volley: [200] NetworkUtility.shouldRetryException: Unexpected response code 400 for ... when getting the presigned URL.
I would appreciate some recommendations for the improvements.
Presigned URLs have restricted expiration time based on how they were signed. For temporary credentials like with cognito it will have a much smaller expiration time than if it was signed using IAM credentials. Your presigned urls expire as soon as the session for the cognito user expires.
https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration/
I'm trying to validate Android/iOS client phone number or email address using Facebook Account-Kit service. I'm not sure how to validate the authorization code or access token with spring boot based back-end server and return the my own access token.
Between, I have gone thorough this blog https://www.baeldung.com/spring-security-5-oauth2-login, but it session based. I'm not clear how to change this to stateless (e.g /oauth/token).
Could anyone please let me know how to solve the issue ?
Reference : [https://developers.facebook.com/docs/accountkit/graphapi][1]
Here is my code :
#Configuration
#EnableOAuth2Client
public class SocialConfig extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
private String[] PUBLIC_URL = { "/*", "/api/v1/account/validate", "login/accountkit", "/api/v1/account" };
#Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.authorizeRequests()
.antMatchers(PUBLIC_URL).permitAll()
.anyRequest().authenticated()
.and().csrf()
.disable()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/accountkit");
OAuth2ProtectedResourceDetails accountkit = accountKit();
OAuth2RestTemplate template = new OAuth2RestTemplate(accountkit, oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices userInfo = new UserInfoTokenServices(accountKitResource().getUserInfoUri(),
accountkit.getClientId());
userInfo.setRestTemplate(template);
filter.setTokenServices(userInfo);
return filter;
}
#Bean
#ConfigurationProperties("accountkit.client")
protected OAuth2ProtectedResourceDetails accountKit() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri("https://graph.accountkit.com/v1.2/me");
resource.setUserAuthorizationUri("https://graph.accountkit.com/v1.2/access_token");
resource.setClientId("AA|xxxx|xxx");
resource.setGrantType("authorization_code");
resource.setTokenName("access_token");
resource.setAuthenticationScheme(AuthenticationScheme.form);
resource.setPreEstablishedRedirectUri("http://localhost:8080/login/accountkit");
return resource;
}
#Bean
#ConfigurationProperties("accountkit.resource")
protected ResourceServerProperties accountKitResource() {
return new ResourceServerProperties();
}
}
I am developing an android app which uses firebase authentication for signin and uses AWS S3 and dynamodb for managing data/images. I am trying to delegate an access to AWS resource via AWSAssumeRoleWebIdentity. The reason I am doing this is AWS Sign-In UI does not allow enough customization for UI and UI flow. I decided to use firebase authentication only for sign-in and sign-up.
Please find the source code and OIDC Provider setting. With them the error log is
No OpenIDConnect provider found in your account for https://securetoken.google.com/[project-name] (Service: AWSSecurityTokenService; Status Code: 400; Error Code: InvalidIdentityToken; Request ID: 37607060-9e1c-11e8-8ae0-636eae27c3bf)
Identity Provider of AWS IAM has been created with the name of "securetoken.google.com/[my-project-name]/" with the Thumbprint that I created referring to [1] and OAuth 2.0 client IDs obtained in Credentials of Google Cloud Service API & Services.
The source code is shown below.
public void uploadImageFile() {
CustomLog.logI("start of uploadImageFile");
setIDToken();
}
private void setIDToken() {
FirebaseUser mUser = FirebaseAuth.getInstance().getCurrentUser();
// To get ID Token of the user authenticated by google authentication
mUser.getIdToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete (#NonNull Task< GetTokenResult > task) {
if (task.isSuccessful()) {
// Token information is set to mIDToken of the global variable
mIDToken = task.getResult().getToken();
AsyncTaskForAssumeRole asyncTaskForAssumeRole = new AsyncTaskForAssumeRole();
asyncTaskForAssumeRole.execute();
} else {
CustomLog.logE(task.getException().getMessage());
}
}
});
}
public class AsyncTaskForAssumeRole extends AsyncTask<Void, Void, BasicSessionCredentials> {
protected BasicSessionCredentials doInBackground(Void... params) {
try {
// set credentials from AssumeRoleWithWebIdentity
BasicSessionCredentials credentials = setAssumeRoleWithWebIdentity();
return credentials;
} catch (Exception e) {
CustomLog.logE(e.getMessage());
return null;
}
}
protected void onPostExecute(BasicSessionCredentials credentials) {
// upload file with S3 connection
connectToS3ForUpload(credentials);
}
}
private BasicSessionCredentials setAssumeRoleWithWebIdentity(){
CustomLog.logD("start of setAssumeRoleWithWebIdentity");
String ROLE_ARN = [my role arn];
// set AssumeRoleWithWebIdentity request with created policy and token information retrieved through Google Sign in information
AssumeRoleWithWebIdentityRequest request = new AssumeRoleWithWebIdentityRequest()
.withWebIdentityToken(mIDToken)
.withRoleArn(ROLE_ARN)
.withRoleSessionName("wifsession");
BasicAWSCredentials basicCreds = new BasicAWSCredentials("", "");
AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(basicCreds);
AssumeRoleWithWebIdentityResult result = sts.assumeRoleWithWebIdentity(request);
Credentials stsCredentials = result.getCredentials();
String subjectFromWIF = result.getSubjectFromWebIdentityToken();
BasicSessionCredentials credentials = new BasicSessionCredentials(stsCredentials.getAccessKeyId(),
stsCredentials.getSecretAccessKey(),
stsCredentials.getSessionToken());
return credentials;
}
Great thanks in advance.
[1] http://docs.aws.amazon.com/cli/latest/reference/iam/create-open-id-connect-provider.html
Consider using Amazon Cognito Federated Identities (Identity Pools) to federate (map) the user from your Identity Provider into Amazon Cognito and obtain a Cognito Identity Id, which can be used to authorize access to AWS resources. See https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html for further details.
Map<String, String> logins = new HashMap<String, String>();
logins.put("login.provider.com", token);
credentialsProvider.setLogins(logins);
Now, you can use the credentialsProvider object with an Amazon S3 client.
AmazonS3 s3Client = new AmazonS3Client(getApplicationContext(), credentialsProvider);
We have some API Gateway endpoints. Each endpoint is protected with a Custom Authorizer (CA). This CA inspect the HTTP call and verify the presence of the Authorization header. This header must contains an OpenId Connect token (a simple JWT token), so the CA can inspect it and make some checks and validations.
When we use Postman to invoke the endpoint, it works without a problem, since we can setup the right headers.
The problems start to raises when we use the generated Android SDK to make the same call, since each attempt to make the call sends an AWS4 signature as header. We can figure out how to send Authorization header with JWT.
We get what we need by extending ApiClientFactory class and adding the header in an explicit ways:
public class CustomApiClientFactory extends ApiClientFactory {
private String LOGIN_NAME = "a.provider.com";
private AWSCredentialsProvider provider;
#Override
public ApiClientFactory credentialsProvider(AWSCredentialsProvider provider) {
this.provider = provider;
return super.credentialsProvider(provider);
}
#Override
ApiClientHandler getHandler(String endpoint, String apiName) {
final Signer signer = provider == null ? null : getSigner(getRegion(endpoint));
// Ensure we always pass a configuration to the handler
final ClientConfiguration configuration = new ClientConfiguration();
return new ApiClientHandler(endpoint, apiName, signer, provider, null, configuration) {
#Override
Request<?> buildRequest(Method method, Object[] args) {
Request<?> request = super.buildRequest(method, args);
request.addHeader("Authorization", "Bearer " + ((CognitoCachingCredentialsProvider) provider).getLogins().get(LOGIN_NAME));
return request;
}
};
}
}
Even if it works, it sounds to me like a workaround. There is some known best practice in facing this problem?
API Gateway allows the request to be defined for each method just add a HTTP request header in the method request and the generated SDK will have a field to specify the header.
Console:
Generated Android SDK:
#com.amazonaws.mobileconnectors.apigateway.annotation.Operation(path = "/companies", method = "GET")
CompanyList companiesGet(
#com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "x-vendor-authorization", location = "header")
String xVendorAuthorization,
#com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "continuationToken", location = "query")
String continuationToken,
#com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "pageSize", location = "query")
String pageSize,
#com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "properties", location = "query")
String properties);
SDK call:
client.companiesGet("abc123AuthToken", "continueToken1", "20", "prop1");
I use retrofit2 in my android apps for any http/rest call. Now I need to call an api generated with Amazon AWS API Gateway.
The AWS documentation say I should generate the client code throw the API Gateway console and use the class ApiClientFactory to build the request:
ApiClientFactory factory = new ApiClientFactory();
// Use CognitoCachingCredentialsProvider to provide AWS credentials
// for the ApiClientFactory
AWSCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
context, // activity context
"identityPoolId", // Cognito identity pool id
Regions.US_EAST_1 // region of Cognito identity pool
};
factory.credentialsProvider(credentialsProvider);
// Create an instance of your SDK (this should come from the generated code).
final MyApiClient client = factory.build(MyApiClient.class);
// Invoke a method (e.g., 'parentPath1Get(param1,body)') exposed by your SDK.
// Here the method's return type is OriginalModel.
OriginalModel output = client.parentPath1Get(param1,body);
// You also have access to your API's models.
OriginalModel myModel = new OriginalModel();
myModel.setStreetAddress(streetAddress);
myModel.setCity(city);
myModel.setState(state);
myModel.setStreetNumber(streetNumber);
myModel.setNested(nested);
myModel.setPoBox(poBox);
Instead I would like to define the API like I would with retrofit: with an interface I write, connect it to RxJava, OkHttp etc...
My question is: how can I sign the retrofit requests with Cognito Identity Provider?
It took me several days to figure out how to make it work. Don't know why they don't point out the class instead of dozen of document pages. There are 4 steps in total, you must call in worker thread, I am using Rxjava but you can use AsyncTask instead:
Observable.create((Observable.OnSubscribe<String>) subscriber -> {
//Step 1: Get credential, ask server team for Identity pool id and regions
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
this, // Context
"Identity Pool ID", // Identity Pool ID
Regions.US_EAST_1 // Region
);
//Step 2: Get these 3 three keys, test with postman v4.9.3 to see if identity is correct
String identityId = credentialsProvider.getIdentityId();
Log.show("identityId = " + identityId);
String AccessKey = credentialsProvider.getCredentials().getAWSAccessKeyId();
String SecretKey = credentialsProvider.getCredentials().getAWSSecretKey();
String SessionKey = credentialsProvider.getCredentials().getSessionToken();
Log.show("AccessKey = " + AccessKey);
Log.show("SecretKey = " + SecretKey);
Log.show("SessionKey = " + SessionKey);
//Step 3: Create an aws requets and sign by using AWS4Signer class
AmazonWebServiceRequest amazonWebServiceRequest = new AmazonWebServiceRequest() {
};
ClientConfiguration clientConfiguration = new ClientConfiguration();
String API_GATEWAY_SERVICE_NAME = "execute-api";
Request request = new DefaultRequest(amazonWebServiceRequest,API_GATEWAY_SERVICE_NAME);
request.setEndpoint(URI.create("YOUR_URI"));
request.setHttpMethod(HttpMethodName.GET);
AWS4Signer signer = new AWS4Signer();
signer.setServiceName(API_GATEWAY_SERVICE_NAME);
signer.setRegionName(Region.getRegion(Regions.US_EAST_1).getName());
signer.sign(request, credentialsProvider.getCredentials());
Log.show("Request header " + request.getHeaders().toString());
//Step 4: Create new request with authorization headers
OkHttpClient httpClient = new OkHttpClient();
Map<String, String> headers = request.getHeaders();
List<String> key = new ArrayList<String>();
List<String> value = new ArrayList<String>();
for (Map.Entry<String, String> entry : headers.entrySet())
{
key.add(entry.getKey());
value.add(entry.getValue());
}
try {
okhttp3.Request request2 = new okhttp3.Request.Builder()
.url("Your_url") // remember to add / to the end of the url, otherwise the signature will be different
.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")
.build();
Response response = null;
response = httpClient.newCall(request2).execute();
String body = response.body().string();
Log.show("response " + body);
} catch (Exception e) {
Log.show("error " + e);
}
subscriber.onNext(identityId);
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.show("Throwable = " + e.getMessage());
}
#Override
public void onNext(String s) {
}
});
The key here is the AWS4Signer class does 4 steps as documented here, you don't need to build one from scratch. In order to use AWS4Signer and AmazonWebServiceRequest, you need to import aws sdk in gradle:
compile 'com.amazonaws:aws-android-sdk-cognito:2.3.9'
Created an OkHttp interceptor based on #thanhbinh84 answer. Give it a try: https://github.com/Ghedeon/AwsInterceptor
The signature process is documented here: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
But you could probably try to reuse some of the code from the core runtime package that the default API Gateway client depends on. There may be libraries out there already for signing requests of type RxJava or OkHttp since the signature process is well-known.