I'm developing an Android app and keep having issues accessing S3 bucket "through" Cognito.
I'm managing to connect to my cognito pool, with a username and a password of a specific user from my pool:
AWSMobileClient auth = AWSMobileClient.getInstance();
auth.signIn("username", "password", new HashMap<String, String>());
if (auth.isSignedIn()) {
ActivityUtils.startActivity(this, MyActivity.class);
}
My user in the pool is assigned to a group with attach S3 role:
Role ARN arn:aws:iam::<my account number>:role/Cognito_S3_Full_Access
Which contains those permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
When I'm trying to use the "AmazonS3Client" I created using the credentials from the "AWSMobileClient" I keep getting errors. I create it in this way:
AWSMobileClient awsMobileClient =AWSMobileClient.getInstance();
AmazonS3Client s3Client = new AmazonS3Client(awsMobileClient);
And the error that I got is:
2019-05-20 17:56:21.645 27019-27019/com.mycomp.mypackage E/S3Uploader: Error upload file to S3 bucket : Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 375C07384A904483)
When I'm creating the "AmazonS3Client" using HARD-CODED accessKey and secretKey of a new "IAM" user that I created and attach the same policy as above it working as expected.
I'm using it this way:
BasicAWSCredentials credentials = new BasicAWSCredentials("myAccessKey_hsjgfhjsdg", "mySecretKey_asdhasjkdha");
AmazonS3Client s3Client = new AmazonS3Client(credentials);
But I don't want to fill up my app with hard coded password, especially that I already manage to connect using the user input of his username and password
Some notes: when I trying to get the secret key and access key after login with Cognito I can see that I get different token every login, so I guess this part is working.
So my question is what am I doing wrong?
How can I make the "AmazonS3Client" to use the session and role permission of the log-in user and his assigned group?
I had everything set up properly and was getting the same Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 8D650E2CCCD58E34)
What I missed was the condition in the S3 template:
"Condition": {
"StringLike": {
"s3:prefix": [
"public/",
"public/*",
"protected/",
"protected/*",
"private/${cognito-identity.amazonaws.com:sub}/",
"private/${cognito-identity.amazonaws.com:sub}/*"
]
}
}
Basically your keys have to start with these prefixes, depending on the policy. I didn't think much of the public/s3Key.txt example key in the Amplify docs.
private val transferUtility = TransferUtility.builder()
.context(context)
.s3Client(AmazonS3Client(AWSMobileClient.getInstance(), Region.getRegion(Regions.EU_WEST_1)))
.awsConfiguration(AWSMobileClient.getInstance().configuration) // use the generated s3 template
.build()
transferUtility.upload("public/$key/${file.name}", file)
Related
I want to use a Firestore database and Datastore in the same Android app. I know that it's not possible to use both in the same GCP project(explained here: Firestore and Datastore in the same GAE project) so I created two different projects, one for the Firestore and one for the Datastore. So now the problem is that I can't create a client Id for my Android app in both projects because of "duplicated fingerprints".
If I had just one project I normally would just login via Firebase AuthUi and then use the token from the FirebaseUser to build my service handler.
//login via FirebaseUI
FirebaseAuth auth = FirebaseAuth.getInstance();
AuthUI.getInstance().createSignInIntentBuilder()
.setTheme(getSelectedTheme())
.setLogo(getSelectedLogo())
.setAvailableProviders(getSelectedProviders())
.setTosAndPrivacyPolicyUrls(getSelectedTosUrl(),getSelectedPrivacyPolicyUrl())
.setIsSmartLockEnabled(true,true)
.build(),
FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
Task<GetTokenResult> tokenResultTask = firebaseUser.getIdToken(false);
tokenResultTask.addOnCompleteListener(task -> {
String result = task.getResult().getToken();
//Creating the service object
GoogleCredential credential = new GoogleCredential();
credential.setAccessToken(result);
EndpointsApi.Builder builder = new EndpointsApi.Builder(
AppConstants.HTTP_TRANSPORT,
AppConstants.JSON_FACTORY, credential);
builder.setApplicationName("endpointsapi-server");
return builder.build();
});
This works very well if I only have one project. But what to do if there are mutliple? The google-services.json is associated with the one project that also has the android client id, but the second project can't create an android client id using the same sha1 and package name(duplicated fingerprints).
If I just use the access token from the first project to build the service object(which belongs to the second project) I just get an error:
401 Unauthorized
{
"code": 401,
"errors": [
{
"domain": "global",
"message": "Authorization required",
"reason": "required"
}
],
"message": "Authorization required"
}
which makes sense, because the second project doesn't know about the first one. Im lost on what to do to somehow use both at the same time.
In my web app I just whitelist the web client of the first project in the second one and then use the credentials of the first, obtained using firebase.AuthUi, to also login the second one. Which works.
this.uiConfig = {
callbacks: {
signInSuccess: (currentUser, credential, redirectUrl) => {
const googleAuthcredential =
firebase.auth.GoogleAuthProvider.credential(credential['idToken']);
firebase.apps[1].auth().signInAndRetrieveDataWithCredential(googleAuthcredential):
}
}
};
this.ui = new firebaseui.auth.AuthUI(firebase.auth());
this.ui.start('#firebaseui-auth-container', this.uiConfig);
How to achieve the same thing in Android?
Thank you very much in advance.
I am creating an android application which connects to AWS IoT using Amazon Cognito authentication. I am able to authenticate user successfully and I am able get the credentials.
While updating the thing shadow using these credentials always return 403 Forbidden Exception. I have tried all my ways to troubleshoot the issue but I found no solutions.
My IAM Policy is:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:GetThingShadow",
"iot:UpdateThingShadow",
],
"Resource": [
"arn:aws:iot:us-west-2:<my_account>:thing/mythingname"
]
}
]
}
Android code for connecting endpoint:
userSession= AppHelper.getCurrSession();
credentialsProvider=new CognitoCachingCredentialsProvider(getApplicationContext(),POOL_ID,REGIONS);
Map<String,String> logins=new HashMap<String, String>();
logins.put("cognito-idp.us-west-2.amazonaws.com/user_pool_id",userSession.getIdToken().getJWTToken());
credentialsProvider.setLogins(logins);
iotDataClient=new AWSIotDataClient(credentialsProvider);
iotDataClient.setEndpoint(ENDPOINT);
Updating thing shadow:
UpdateThingShadowRequest request=new UpdateThingShadowRequest();
request.setThingName(thingName);
ByteBuffer payloadBuffer=ByteBuffer.wrap(updateState.getBytes());
request.setPayload(payloadBuffer);
UpdateThingShadowResult result=iotDataClient.updateThingShadow(request);
Any help with this regard would be appreciated.
I had the same issue as you. I've found a solution.
That 403 status code mean that you need authorization.
If you read this documentation (near the end) : Publish/Subscribe Policy Exemple it's stated that you need 2 policies to make it work with Authenticated Cognito User. One for the Cognito Identity Pool and another for the Cognito User.
It's not possible to attach a policy to a cognito user with the UI, but you can do it through the CLI.
The command to attach a policy to a cognito user is :
aws iot attach-principal-policy --principal "cognito user id" --policy-Name "policy name"
You can find your cognito user id in :
Cognito > Manager Federated Identities > choose your identity pool > identity browser > and find your identity ID
I use this policy for testing purpose.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:*"
],
"Resource": [
"*"
]
}
]
}
To make it reusable, you need to use a lambda function (here in JavaScript).
var AWS = require('aws-sdk');
var iot = new AWS.Iot();
exports.handler = function(event, context, cb) {
var params = {
policyName: 'your policy',
principal: 'your cognito id'
};
var out = iot.attachPrincipalPolicy(params, function(err, data) {
if (err) cb(err);
else cb(null, data);
});
};
I am able to identify the issue. In my case, I was missing to set region for AWS iot client.
Region region = Region.getRegion(MY_REGION);
mIotAndroidClient.setRegion(region); // I was missing this piece of code
I am trying to generate server auth code in android
gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestScopes(new Scope(Scopes.DRIVE_APPFOLDER))
.requestServerAuthCode(getString(R.string.server_client_id), false)
.build();
Then I have tried to get server auth code like this.
result.requestServerAuthCode(getString(R.string.server_client_id), false)
Suppose i got a auth token like 'bla bla bla';
Then using laravel socialite I am trying to get user on the server side
Socialize::driver('google')->userFromToken('bla bla bla')
It shows me error
GuzzleHttp\Exception\ClientException with message 'Client error: GET https://www.googleapis.com/plus/v1/people/me?prettyPrint=false resulted in a 401 Unauthorized` response:
{"error":{"errors":[{"domain":"global","reason":"authError","message":"Invalid Credentials","locationType":"header","loc (truncated...)
Actually the code sent by google on android is not the access token to get access token you can do this on laravel controller.
Install this composer library https://github.com/pulkitjalan/google-apiclient
$client = new \PulkitJalan\Google\Client(['client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_SECRET', 'redirect_uri' => 'YOUR_REDIRECT_URI', 'developer_key' => 'YOUR_KEY']);
$google = $client->getClient();
$google->authenticate($token);
$access_token = $client->getAccessToken()["access_token"];
//and now here you go
$user = Socialize::driver('google')->userFromToken($access_token);
The easiest way is to use the Google_Client class that is provided from Google. you can find it here
Now, you've got two option:
You can send an id_token to the server, and validate it just as the link said and then just fetch the user info from the object received.
You can send the backend_auth_code to the server instead of the id_token, and then use the method fetchAccessTokenWithAuthCode($code) of the Google_Client class. This gives you the access token, id token and other stuff. You can then use that access_token with Laravel Socialite
$client = new Google_Client([
'client_id' => config('services.google.client_id'),
'client_secret' => config('services.google.client_secret')
]);
$data = $client->fetchAccessTokenWithAuthCode($code);
$user = Socialite::driver('google')->scopes(['profile','email'])->userFromToken($data['access_token']);
I am using Slim framework to return JSON to my Android device. I am currently working on login on my device. I am using 3 different ways to login: Facebook, Google and account login. When he takes account login he can register a new account or login with an existing one.
For security on my web service I thought to use JWT security. So I am reading and watching video's about how it works. I think I understand how it works, but I cannot find anything about how to implement it correctly.
The middleware I use for slim v3 is called: Slim-JWT-Auth.
I found the following link to implement this in my slim framework, and it works correctly I think.
Now my questions:
How do I generate my Token?
When do I generate my Token?
Do I also need a Token when using Google or Facebook sign-in? because they already use a Auth2.0 token?
I understand how it works but nobody is talking about when and how to implement it. So when do I need to generate the token (on login on the webservice?), and do I need to generate a token after every start of the app, or do I just need to wait until the token expires?
How do I generate my Token?
Since the middleware already includes firebase/php-jwt library you can use it to generate the token.
$now = new DateTime();
$future = new DateTime("now +2 hours");
$server = $request->getServerParams();
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"sub" => $server["PHP_AUTH_USER"]
];
$secret = "supersecretkeyyoushouldnotcommittogithub";
$token = JWT::encode($payload, $secret, "HS256");
When do I generate my Token?
In your api you can for example include a password protected route which returns the token. All other routes except /token are JWT authenticated. Client can request token with every request or just always bit before the old one expires.
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => "/token",
"users" => [
"test" => "test"
]
]);
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub"
"rules" => [
new RequestPathRule([
"path" => "/",
"passthrough" => ["/token"]
])
]
]);
$app->post("/token", function ($request, $response, $arguments) {
$now = new DateTime();
$future = new DateTime("now +2 hours");
$server = $request->getServerParams();
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"sub" => $server["PHP_AUTH_USER"],
];
$secret = "supersecretkeyyoushouldnotcommittogithub";
$token = JWT::encode($payload, $secret, "HS256");
$data["status"] = "ok";
$data["token"] = $token;
return $response->withStatus(201)
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
});
Do I also need a Token when using Google or Facebook sign-in? because they already use a Auth2.0 token?
There is no clear answer to this. It "depends". You could for example authenticate your /token route with Facebook or Google and return your own JWT token from there.
There is an work in progress more detailed example implementation of everything above you might want to check.
I am using https://github.com/Wizcorp/phonegap-facebook-plugin to connect my cordova Android app to Facebook.
I can login with:
facebookConnectPlugin.login(["email"],
fbLoginSuccess,
function (error) { alert("ERROR:" + JSON.stringify(error)); }
);
, logout even call facebookConnectPlugin.getLoginStatus and get:
userID
accessToken
expiresIn
sig
status
but when FB.api('/me', function(response){...}) is called, I receive
{error:
{
message: An active access token must be used to query information about the current user,
type: OAuthException,
code: 2500
}
}
Also this only happens when the app is built, not tested in browser.
Solved issue by manually giving FB.apicall a token with:
FB.api('/me?access_token='+userdata.authResponse.accessToken, function(response) {...
where userdata is the response of facebookConnectPlugin.getLoginStatus
I just don't understand why doesn't it provide token automatically in android app like it does in browser.
Send the access token and the data retrieving fields with the apiString
const fields = ['email', 'first_name', 'last_name', 'gender', 'birthday', 'picture.type(large)'];
const apiString = `me?access_token=${accessToken}&fields=${fields.join(',')}`;