AWS API Gateway Custom Authorizer based on User Groups - android

I'm attempting to design a system where users are created in my AWS user pool and assigned to one of four user groups. These user groups have roles attached to them which specify the API Calls they are allowed to make. I've created a user for each group and I'm able to successfully log into them in my Android Application. My User Pool is also attached to an Identity Pool for handling Single Sign On with Identity Federation.
The problem is that rather than assuming the Role assigned to the user group, when I log into the user, the role assigned to the user seems to be coming from the Identity Pool rather than their User Group, and as a result they're unable to make the api calls that they should have access to.
I'm attempting to fix this by implementing a Custom Authorizer in Node.js, but the script appears to be running into some problems. Whenever it enters the ValidateToken() method, it fails saying that the token isn't a JWT token.
console.log('Loading function');
var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');
var groupName = 'MY_GROUP_NAME';
var roleName = 'MY_ROLE_NAME';
var policyName = 'MY_POLICY_NAME';
var userPoolId = 'MY_USER_POOL_ID';
var region = 'MY_REGION';
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
var pems;
exports.handler = function(event, context) {
//Download PEM for your UserPool if not already downloaded
if (!pems) {
//Download the JWKs and save it as PEM
request({
url: iss + '/.well-known/jwks.json',
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['keys'];
for(var i = 0; i < keys.length; i++) {
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
//Now continue with validating the token
ValidateToken(pems, event, context);
} else {
//Unable to download JWKs, fail the call
context.fail("error");
}
});
} else {
//PEMs are already downloaded, continue with validating the token
ValidateToken(pems, event, context);
};
};
function ValidateToken(pems, event, context) {
var token = event.authorizationToken;
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
//THIS IS WHERE THE SCRIPT ENDS UP
console.log("Not a valid JWT token");
context.fail("Unauthorized - Invalid Token Provided");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized - Invalid Issuer Provided");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'access') {
console.log("Not an access token");
context.fail("Unauthorized - Not an Access Token");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid access token');
context.fail("Unauthorized - Invalid Access Token Provided");
return;
}
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
if(err) {
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not verify token signature");
}
else {
//Valid token. Generate the API Gateway policy for the user
//Always generate the policy on value of 'sub' claim and not for 'username' because username is reassignable
//sub is UUID for a user which is never reassigned to another user.
var principalId = payload.sub;
var username = payload.username;
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: userPoolId, /* ID of the Target User Pool */
Username: username, /* Provided by event object??? */
Limit: 0,
NextToken: '' //May need actual token value
};
cognitoidentityserviceprovider.adminListGroupsForUser(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not obtain Groups for User");
}
else{
var groups = data.Groups;
var numGroups = groups.length;
var isFound = false;
for(var i = 0; i < numGroups; i++){
if(groups[i].GroupName == groupName){
isFound = true;
}
}
if(isFound){
var iam = new AWS.IAM();
var iamParams = {
PolicyName: policyName, /* Name of the Policy in the User Group Role */
RoleName: roleName /* Name of the User Group Role */
};
iam.getRolePolicy(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not acquire Policy for User Group Role");
}
else {
var policy = data.PolicyDocument;
context.succeed(policy); //May need to build policy
}
});
}
else{
context.fail("Unauthorized - Could not find the required User Group under the User");
}
}
});
}
});
}
Can anybody identify the problem with this script, or perhaps help me identify why the tokens being set aren't valid JWT tokens? The tokens are sent by an Android Application using the AWS Cognito SDK.
EDIT: Upon further investigation, the token retrieved from event.authorizationToken is of the following format (the [VALUE] blocks are to hide potentially sensitive information):
AWS4-HMAC-SHA256 Credential=[VALUE1]/20170329/us-east-1/execute-api/aws4_request,
SignedHeaders=host;x-amz-date;x-amz-security-token,
Signature=[VALUE2]

If clients are getting the AWS credentials after login, you can only use AWS_IAM authorization type on the API Gateway Methods. The authorizationToken value you are seeing is the AWS signature generated by the client using the credentials vended by Cognito. It will not be possible for you to validate the AWS signature in a custom authorizer.
Are you following this Cognito blog post? If so, I think you might be confusing the User Group role with the authenticated role selection on the Identity Pool. When you use the federated identities with User Pool provider, your client will get back AWS credentials that have the permissions of the 'Authenticated role' from that section in the Cognito tab in the Identity Pool. In the blog post this would be the 'EngineerRole' set on the Identity Pool.

I figured it out:
This document (specifically the bottom part) says "If you set roles for groups in an Amazon Cognito user pool, those roles are passed through the user's ID token. To use these roles, you must also set Choose role from token for the authenticated role selection for the identity pool."
All it takes is to set the appropriate Trust Policy on each role, adjust the Identity Pool to use "Choose role from token" with the user pool authentication provider, and the proper roles are now being assumed. For others running into this problem, here is my trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "[IDENTITY_POOL_ID]"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}

Related

InvalidConfigurationException: Identity pool isn't set up for SNS using AmazonCognitoSync service

I have been trying to use AWS SDKs for Push Notifications. But I am getting errors. Tried to find a solution, but can't find much support for this.
iOS & Web push notifications are working fine
What all is already setup & done:
AWS back-end & console setting in place.
Identity Pool Id & other keys in place.
ARN topic in place.
Android side:
AWS SDK dependencies:
implementation 'com.amazonaws:aws-android-sdk-core:2.16.8'
implementation 'com.amazonaws:aws-android-sdk-cognito:2.6.23'
implementation 'com.amazonaws:aws-android-sdk-s3:2.15.1'
implementation 'com.amazonaws:aws-android-sdk-ddb:2.2.0'
implementation ('com.amazonaws:aws-android-sdk-mobile-client:2.16.8') { transitive = true; }
minSdkVersion 21
targetSdkVersion 29
Inside onCreate:
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(),
"My Pool Id here", // Identity pool ID
Regions.US_EAST_1 // Region
);
CognitoSyncManager client = new CognitoSyncManager(
LoginActivateActivity.this,
Regions.US_EAST_1,
credentialsProvider);
String registrationId = "MY_FCM_DEVICE_TOKEN"; **Instead of GCM ID, I am passing my unique FCM device token here. I searched, & it seems that wherever GCM is required, it is being replaced by FCM.**
try {
client.registerDevice("GCM", registrationId);
} catch (RegistrationFailedException rfe) {
Log.e("TAG", "Failed to register device for silent sync", rfe);
} catch (AmazonClientException ace) {
Log.e("TAG", "An unknown error caused registration for silent sync to fail", ace);
}
Dataset trackedDataset = client.openOrCreateDataset("My Topic here");
if (client.isDeviceRegistered()) {
try {
trackedDataset.subscribe();
} catch (SubscribeFailedException sfe) {
Log.e("TAG", "Failed to subscribe to datasets", sfe);
} catch (AmazonClientException ace) {
Log.e("TAG", "An unknown error caused the subscription to fail", ace);
}
}
Error I am getting on client.registerDevice("GCM", registrationId);
Caused by: com.amazonaws.services.cognitosync.model.InvalidConfigurationException: Identity pool isn't set up for SNS (Service: AmazonCognitoSync; Status Code: 400; Error Code: InvalidConfigurationException; Request ID: a858aaa2-**************************)
Note:
I tried using Amplify libraries, but even that didn't work. Also, at iOS & Web end they are using AWS SDK. So I am also bound to use the same. This is not even a device specific error.
All I need to do is setup my project to get push notifications. But I am stuck at the initial step. Not able to create an endpoint for Android device.
I actually found the solution to the issue, thanks to a friend who shared this link:
https://aws.amazon.com/premiumsupport/knowledge-center/create-android-push-messaging-sns/
This Youtube video also helped a lot:
https://www.youtube.com/watch?v=9QSO3ghSUNk&list=WL&index=3
Edited code
private void registerWithSNS() {
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(),
"Your Identity Pool ID",
Regions.US_EAST_1 // Region
);
client = new AmazonSNSClient(credentialsProvider);
String endpointArn = retrieveEndpointArn();
String token = "Your FCM Registration ID generated for the device";
boolean updateNeeded = false;
boolean createNeeded = (null == endpointArn || "".equalsIgnoreCase(endpointArn));
if (createNeeded) {
// No platform endpoint ARN is stored; need to call createEndpoint.
endpointArn = createEndpoint(token);
createNeeded = false;
}
System.out.println("Retrieving platform endpoint data...");
// Look up the platform endpoint and make sure the data in it is current, even if
// it was just created.
try {
GetEndpointAttributesRequest geaReq =
new GetEndpointAttributesRequest()
.withEndpointArn(endpointArn);
GetEndpointAttributesResult geaRes =
client.getEndpointAttributes(geaReq);
updateNeeded = !geaRes.getAttributes().get("Token").equals(token)
|| !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
} catch (NotFoundException nfe) {
// We had a stored ARN, but the platform endpoint associated with it
// disappeared. Recreate it.
createNeeded = true;
} catch (AmazonClientException e) {
createNeeded = true;
}
if (createNeeded) {
createEndpoint(token);
}
System.out.println("updateNeeded = " + updateNeeded);
if (updateNeeded) {
// The platform endpoint is out of sync with the current data;
// update the token and enable it.
System.out.println("Updating platform endpoint " + endpointArn);
Map attribs = new HashMap();
attribs.put("Token", token);
attribs.put("Enabled", "true");
SetEndpointAttributesRequest saeReq =
new SetEndpointAttributesRequest()
.withEndpointArn(endpointArn)
.withAttributes(attribs);
client.setEndpointAttributes(saeReq);
}
}
/**
* #return never null
* */
private String createEndpoint(String token) {
String endpointArn = null;
try {
System.out.println("Creating platform endpoint with token " + token);
CreatePlatformEndpointRequest cpeReq =
new CreatePlatformEndpointRequest()
.withPlatformApplicationArn("Your Platform ARN. This you get from AWS Console. Unique for all devices for a platform.")
.withToken(token);
CreatePlatformEndpointResult cpeRes = client
.createPlatformEndpoint(cpeReq);
endpointArn = cpeRes.getEndpointArn();
} catch (InvalidParameterException ipe) {
String message = ipe.getErrorMessage();
System.out.println("Exception message: " + message);
Pattern p = Pattern
.compile(".*Endpoint (arn:aws:sns[^ ]+) already exists " +
"with the same [Tt]oken.*");
Matcher m = p.matcher(message);
if (m.matches()) {
// The platform endpoint already exists for this token, but with
// additional custom data that
// createEndpoint doesn't want to overwrite. Just use the
// existing platform endpoint.
endpointArn = m.group(1);
} else {
// Rethrow the exception, the input is actually bad.
throw ipe;
}
}
storeEndpointArn(endpointArn);
return endpointArn;
}
/**
* #return the ARN the app was registered under previously, or null if no
* platform endpoint ARN is stored.
*/
private String retrieveEndpointArn() {
// Retrieve the platform endpoint ARN from permanent storage,
// or return null if null is stored.
return endpointArn;
}
/**
* Stores the platform endpoint ARN in permanent storage for lookup next time.
* */
private void storeEndpointArn(String endpointArn) {
// Write the platform endpoint ARN to permanent storage.
UserSession.getSession(LoginActivateActivity.this).setARN(endpointArn); //Your platform endpoint ARN. This is unique for each device, but changes when
}
Once an endpoint is created for the device, you need to store the endpointArn & FCM registration ID to your DB on server-side. Rest of the code will be your FCM implementation code for receiving notifications.
Hope this helps someone

Response from Purchases.Subscriptions.Get have EmailAddress, FamilyName and GivenName fields returned null

Android app Akita Security in Google Play
https://play.google.com/store/apps/details?id=com.highiot.mob
Application have subscription to purchase. After the user purchase it from his device, I see the purchase token of his purchase and call publisherService.Purchases.Subscriptions.Get
to see name and email of user that purchased my subscription.
My problem is that EmailAddress, GivenName and FamilyName fields that returned in the response to Get are null.
Other fields like price, country etc. are OK.
Code of purchase in my app:
private async void Purcase(string productId)
{
try
{
var connected = await CrossInAppBilling.Current.ConnectAsync();
if (!connected)
{
//Couldn't connect to billing, could be offline, alert user
return;
}
//try to purchase item
CrossInAppBilling.Current.InTestingMode = true;
var purchase = await CrossInAppBilling.Current.PurchaseAsync(productId, ItemType.Subscription, "apppayload");
if (purchase == null)
{
//Not purchased, alert the user
}
else
{
//Purchased, save this information
var id = purchase.Id;
var token = purchase.PurchaseToken;
var state = purchase.State;
bool status = false;
if (state == PurchaseState.PaymentPending)
{
status = true;
}
ViewModel.SendSubscription(productId, token, "Google", status);
}
}
catch (Exception ex)
{
//Something bad has occurred, alert user
}
finally
{
//Disconnect, it is okay if we never connected
await CrossInAppBilling.Current.DisconnectAsync();
}
}
Code when I call Google API Subscriptions.Get:
static async Task Main(string[] args)
{
bool acknowledge = false;
string projectId = "robotic-circle-243009";
string subscriptionId = "highiot-admin-api";
var _credentialsJson = "HighIoT_key.json";
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _credentialsJson));
SubscriptionName subscriptionName = new SubscriptionName(projectId, subscriptionId);
SubscriberClient subscriber = await SubscriberClient.CreateAsync(subscriptionName);
GoogleCredential credentialsPlay;
using (var key = new FileStream(_credentialsJson, FileMode.Open, FileAccess.Read))
credentialsPlay = GoogleCredential.FromStream(key).CreateScoped(Scope.Androidpublisher);
var publisherService = new AndroidPublisherService(new BaseClientService.Initializer
{
HttpClientInitializer = credentialsPlay
});
var request1 = publisherService.Purchases.Subscriptions.Get("com.highiot.mob", "com.highiot.mob.test_subsription", "nceeeoihojjngafdmfmahbii.AO-J1Ow8yISTj4C6MW4mKgXYp9Tizo7iZ8JP6PgIRenFLHj4xHaAuCxffDL5jaxEzq_t2IopZXXa4_uLyZYBsXMwk_wn3pwMXoL9rNDWJoZV8SCkVsXcAlaMJe0l2Mn_-OhhuHbjPVP8Tn0ruP4WUlxGPyxUjkNv_Q");
var response1 = request1.Execute();
}
Response I get in response1:
Name Value
◢ response1 {Google.Apis.AndroidPublisher.v3.Data.SubscriptionPurchase}
AcknowledgementState 1
AutoRenewing false
AutoResumeTimeMillis null
CancelReason 0
▶ CancelSurveyResult {Google.Apis.AndroidPublisher.v3.Data.SubscriptionCancelSurveyResult}
CountryCode “UA”
DeveloperPayload “apppayload”
ETag “\”kLoaNGFQwzHXpxRSLTfX9req9yE/1xXhq0Lm_lUloQUDN3UhbVIoE08\””
EmailAddress null
ExpiryTimeMillis 1568883651389
FamilyName null
GivenName null
Kind “androidpublisher#subscriptionPurchase”
LinkedPurchaseToken null
OrderId “GPA.3309-8881-6545-64850”
PaymentState 1
PriceAmountMicros 21990000
PriceChange null
PriceCurrencyCode “UAH”
ProfileId null
ProfileName null
PurchaseType null
StartTimeMillis 1566205275451
UserCancellationTimeMillis 1566209506502
Credentials file HighIoT_key.json:
{
"type": "service_account",
"project_id": "robotic-circle-243009",
"private_key_id": "cb017791695e97d8b2ab68ae94d940ba07def824",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPJxCrGu1o1AdD\neiBtuxPS/0wdF4chN5KqK0hR8E6x7qfd30I9zI5k07msD3qEo2iuBWwohIeUCWLh\n5x0Xkkx8ADQqWh9Z0HlWcx8JEFIqeKGy+l7j8mnLtv71xxnUSZdPuE7VQhNt9U2F\ng/uCehcF5rRYKsb9m//uS1dzAFfPepnwgOyAbxL91EUX8TFpfjqonof+pV4N4kTd\nT+U6YcK9FEyQCBUIMW3SJRYQE3lVjxKOVI7osoh3ileMMyFmYhROkuHgL4dMijHa\nXtI6d+8ZkbBtmGy28yYFzMwFKdyb5r9locpcpfXRJnRFRgEHRkGBkvAjdQo/KeNp\nznDsEzKlAgMBAAECggEAMrYJ/xEm+EgbhRnDEYnAFyGXVPHbCzcrCMxytlhhM27M\nmmSatz05yJYKOIRLhmfoat159ktHU2ae1dV5ijkuzxZPruSEEz0VTyP8f4AG1U3V\nq9Zd32iLHKsuLCWy/YSMbaAdcFhuIDdyZFKEghL4SuII2SKTtyb21rOL82Osm2Yj\niiB+04huCKOXvau86284c6GuTtUCRRGPuT85LsV4gQvSLa9gSm9UrMonKktXjdRA\nZenOYtofc4gVjIFozHZwLWleSaM/9jaX0IJ7uct3G1DObqEptCfQnSRQsa1Mtv6e\nXSazGPYMAGW+XOF/yWwuM0zCyI3GyEbc989PI/4IuwKBgQD1Yz3qAeQGYMToLNb7\nUMd0OtWxJFHHa2J97Dwlv73/aGroByAkc4bmYo2ub1YW/JVcyu94IfNR0wHGiV07\ncwFOkYlEYDMWv11VMBeqhHrLmk242jM1ZxhIoNC59RcSBnQHnbvHCe2FugKH7s51\nSHV8EUWR+CkIpZRLNIJBEXv4BwKBgQDYHIMBJ0vYE0O+EvJHR3DQrOQN4xLb2GlI\nFvN1+6N2oox5yfXUlHLi9g138cJnqpayERGE9lZfphSoAtgcgidwJDx7VCkf5C+Q\n8P4wkgL24svZhcJJI2xmnTV8LrG6nU81bUu5dyGTnFeeVPQLcoscVKyII1NbXPBc\nIPbmTAoc8wKBgEl7K2osoMTMK4q/Cq19Ngz3JVCDaL3HyVmd6TEApvuZoBHVOGnR\n8n6A2p52lpgRkQSVfHpD6GIDRs/WCIYp3SVS7YD9Ma9JJMDCwwN74m2fylBlftoq\nAhqVlYtp+jualRpGwJlMvsTBu8pK16ZFSXEalvOsFVWSh8KxeaPUgYP3AoGAT0oe\nspSlWhCvYRR4ebh3ZsiYH5Q5fhmnfwCUsKvzrHo1ChYUMLuKb0URafl0dy56fbiP\ncfYjeJJpr77jZYpHR/izjBgzwnSpEweoa0+W8NgDLLrLrqPliLyTPA2xvaMrxZFl\nIBXaZtsMtpW6uFx9N5bFemljkvjFYzfg/lvVtgsCgYEAqMQ8CEjizn91k8ACK6Fs\nFQkCLjSl5h/lvES0+VZDlWMIwos0e6a7vsTVg0IoJ32hY2a7E6R37MbHP2o6lJiS\njJPzBd5pa803DYl8ehId0/8PAUsMscK6LKPblQW9QzoYk5yEjhU1bFLJQfLmmQsV\neRHZgYsNE/U60DnuZsv4wZg=\n-----END PRIVATE KEY-----\n",
"client_email": "highiot-server#robotic-circle-243009.iam.gserviceaccount.com",
"client_id": "110565816019675715733",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/highiot-server%40robotic-circle-243009.iam.gserviceaccount.com"
}
On Google Play Console:
I link the project to the google play console
I add the Service account to the google play console
It's present in the user menu of google play console
ON GOOGLE API DEVELOPER CONSOLE
I give him permission
In google developer console I gave permission to the service account
And of course I've enabled the google Play Android Developer Api
And added scope to see user info
Apologies for the confusion, but the fields you mention are only for when users Subscribe with Google. For an exhaustive list of fields that and the conditions under which they are available, please see https://developers.google.com/android-publisher/api-ref/purchases/subscriptions

aws cognito user get id token android

I'm trying to get the current logged in user's id token for aws cognito in android.
I found this example:
session.getIdToken().getJWTToken()
where session is a CognitoUserSession object
I can't seem to figure out a way to get the current cognitousersession after the login call has been made.
I'm using the default authenticator activity from the notes tutorial:
https://docs.aws.amazon.com/aws-mobile/latest/developerguide/tutorial-android-aws-mobile-notes-auth.html
It says that the tokens are stored in the shared preferences, but I can't figure out how to retrieve them on future activities so that I can make calls to the api gateway using the id token.
The AWS Android SDK will return the JWT token without a network call when the token is not/will not expire.
The threshold for when a token should be refreshed can be set with the CognitoIdentityProviderClientConfig.setRefreshThreshold(long) method.
If you are stil curious how to retrieve the token yourself, then the code can be found in readCachedTokens() method
https://github.com/aws/aws-sdk-android/blob/master/aws-android-sdk-cognitoidentityprovider/src/main/java/com/amazonaws/mobileconnectors/cognitoidentityprovider/CognitoUser.java#L2116
As nobody has answered yet, this might help you out, be aware this is JS code:
This is my routine to receive the session from an already logged in user.
after this, i'm able to access tokens.
var user_data = {
UserPoolId: AWSConfiguration.UserPoolId,
ClientId: AWSConfiguration.ClientAppId
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(user_data);
if (userPool.getCurrentUser() != null) {
userPool.getCurrentUser().getSession(function (err, session) {
if (err) {
window.location.href = "login.html";
}
var user_params = {
IdentityPoolId: AWSConfiguration.IdPoolId,
Logins: {
'cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXX':session.idToken.jwtToken
}
};
AWS.config.credentials = new AWS.CognitoIdentityCredentials(user_params);
AWS.config.region = AWSConfiguration.region;
AWS.config.credentials.refresh((error) => {
if (error) {
console.error(error);
}
else {
user_is_authenticated();
}
});
});
}

Authentication - Xamarin.Forms, Azure Mobile Apps

I've got a Xamarin.Forms app (iOS, Android, UWP) connected to Azure Mobile Apps' authentication service. From what I read, it seemed pretty straightforward to implement. I'm testing in a UWP project, and Android, both get the same result.
When logging in, I get this lovely "can't connect to the service you need right now." I don't see anything wrong in my code. What might be going on here?
Windows UWP:
public async Task<bool> Authenticate()
{
string message = string.Empty;
var success = false;
try
{
// Sign in with Facebook login using a server-managed flow.
if (user == null)
{
user = await TaskService.DefaultService.CurrentClient.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
if (user != null)
{
success = true;
message = string.Format("You are now signed-in as {0}.", user.UserId);
}
}
}
catch (Exception ex)
{
message = string.Format("Authentication Failed: {0}", ex.Message);
}
// Display the success or failure message.
await new MessageDialog(message, "Sign-in result").ShowAsync();
return success;
}
Android:
public async Task<bool> Authenticate()
{
var success = false;
var message = string.Empty;
try
{
// Sign in with Facebook login using a server-managed flow.
user = await TaskService.DefaultService.CurrentClient.LoginAsync(this,
MobileServiceAuthenticationProvider.Facebook);
if (user != null)
{
message = string.Format("you are now signed-in as {0}.",
user.UserId);
success = true;
}
}
catch (Exception ex)
{
message = ex.Message;
}
// Display the success or failure message.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle("Sign-in result");
builder.Create().Show();
return success;
}
Can't connect to service error:
The settings on Facebook for the app:
Visited:
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started-users/
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-facebook-authentication/
https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/azure/
EDIT:
I've tried moving the client code to App.cs:
private static MobileServiceClient _Client;
public static MobileServiceClient Client
{
get
{
if(_Client == null) _Client = new MobileServiceClient(AppConstants.AzureMobileServiceURL);
return _Client;
}
set { _Client = value; }
}
And the new call in MainPage.xaml.cs:
uesr = await Slated.App.Client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
Same result!
Another note: My Azure Mobile Services URL does include https://
EDIT 2:
Code below for logging on backend, looks to be an issue when redirecting to /login/facebook, the rest of the authentication appears to proceed OK. FYI - replaced the sensitive data with ____
2016-07-29T18:38:43 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:43 PID[6684] Information Redirecting: https://www.facebook.com/dialog/oauth?response_type=code&client_id=_____________&redirect_uri=https%3A%2F%2F________.azurewebsites.net%2F.auth%2Flogin%2Ffacebook%2Fcallback&scope=public_profile&state=_____________________________&display=popup
2016-07-29T18:38:50 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/.auth/login/facebook/callback?code=____________________
2016-07-29T18:38:50 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/oauth/access_token.
2016-07-29T18:38:51 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/oauth/access_token.
2016-07-29T18:38:51 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/me.
2016-07-29T18:38:51 PID[6684] Information Login completed for 'Thomas Gardner'. Provider: 'facebook'.
2016-07-29T18:38:51 PID[6684] Verbose Writing 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:51 PID[6684] Information Redirecting: https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:51 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:51 PID[6684] Verbose Found 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:51 PID[6684] Verbose Authenticated Thomas Gardner successfully using 'Session Cookie' authentication.
2016-07-29T18:38:52 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:52 PID[6684] Verbose Found 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:52 PID[6684] Verbose Authenticated __________ successfully using 'Session Cookie' authentication.
2016-07-29T18:38:52 PID[6684] Information Request, Method=GET, Url=https://________.azurewebsites.net/login/facebook, Message='https://________.azurewebsites.net/login/facebook'
2016-07-29T18:38:52 PID[6684] Information Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
2016-07-29T18:38:52 PID[6684] Information Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
2016-07-29T18:38:52 PID[6684] Information Response, Status=404 (NotFound), Method=GET, Url=https://________.azurewebsites.net/login/facebook, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
Backend Code: It is copied from MSFT's template code. Not much has been done to the startup.
Startup.Mobile.App.cs
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//For more information on Web API tracing, see http://go.microsoft.com/fwlink/?LinkId=620686
config.EnableSystemDiagnosticsTracing();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new SlatedInitializer());
// To prevent Entity Framework from modifying your database schema, use a null database initializer
// Database.SetInitializer<SlatedContext>(null);
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
public class SlatedInitializer : CreateDatabaseIfNotExists<SlatedContext>
{
protected override void Seed(SlatedContext context)
{
/*List<Tasks> todoItems = new List<Tasks>
{
new Tasks { Id = Guid.NewGuid().ToString(), Text = "First item", Complete = false },
new Tasks { Id = Guid.NewGuid().ToString(), Text = "Second item", Complete = false },
};
foreach (Tasks todoItem in todoItems)
{
context.Set<Tasks>().Add(todoItem);
}*/
base.Seed(context);
}
}
From the URIs that are being used, it looks like you are mixing packages between Mobile Services and Mobile Apps. The two are not compatible.
To learn more, see Client and server versioning in Mobile Apps and Mobile Services.
On the server, you should be using Microsoft.Azure.Mobile.Server.*. Make sure you have no packages in the form WindowsAzure.MobileServices.Backend.
On the client, you must use the package Microsoft.Azure.Mobile.Client.
user = await App.MobileService .LoginAsync(MobileServiceAuthenticationProvider.Facebook);
try this line of code.This should work and for the user id
you can simply do it like that :
var userId = user.Id;
Well here how I do it : in the app.xaml.cs
public static MobileServiceClient MobileService = new MobileServiceClient("https://yourmobileservices.azurewebsites.net");
then in your code behind:
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
and it works like a charm for me,And make sure that in azure portal in the
Authentication tab that under advanced settings that Token store is on.

Firebase : Permission denied - while setValue()

I am trying to build a chat application using firebase.
The structure for message table :
message -
$message_id
- $message_push_id
- message {
sender : 3,
receiver : 58,
token : token_of_sender,
message : hi
....}
message_id here is generated using the sender and receiver ids "3_58"
I am using push to save messages into firebase.
{
"rules": {
".read": true,
"message":
{
"$messageid": {
"$messagepushid":
{
".read": true,
".write": "auth != null && !data.exists()",
".indexOn": ["token", "userid", "receiverid", "sent_time"],
".validate": "auth.token == newData.child('token').val() && newData.hasChildren(['token', 'userid', 'receiverid', 'text'])"
}
}
}
}
}
I have already generated token using custom token generator :
Firebase firebase = getFirebase();
Map<String, Object> authPayload = new HashMap<String, Object>();
authPayload.put("uid", user.getUserid());
authPayload.put("token", user.getToken());
TokenGenerator tokenGenerator = new TokenGenerator(Constants.FIREBASE_KEY);
TokenOptions tokenOptions = new TokenOptions();
tokenOptions.setAdmin(false);
final String firebaseToken = tokenGenerator.createToken(authPayload, tokenOptions);
firebase.authWithCustomToken(firebaseToken, new Firebase.AuthResultHandler() {
#Override
public void onAuthenticated(AuthData authData) {
Log.d("Auth", "Success : " + authData.toString());
Log.d("Auth", "Token : " + firebaseToken);
SharedPrefs.setFirebaseUserToken(getActivity(), firebaseToken);
}
#Override
public void onAuthenticationError(FirebaseError
firebaseError) {
firebaseError.toException().printStackTrace();
}
});
I am trying to push a new message but I am getting error :
RepoOperation﹕ setValue at /message/3_58/-Jy2We4cqLjuQNF6Oyhs failed: FirebaseError: Permission denied
I am unable to figure out where I am going wrong.
This is the code to send chat :
mConversationReferenceFireBase = mFireBase.child("message").child(mConversationId);
Chat conversation = new Chat( mToken, mUserId, mReceiverId, message );
mConversationReferenceFireBase.push().setValue(conversation, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
Log.e("Conversation", firebaseError.toString());
}
}
});
mConversationId = 3_58
The token here is generated for a user. We have a separate server to maintain the user accounts. The token is being used to upload/ download any files, the firebase is used as Chat Server.
With the rules set to .read = true and .write = true; everything works, however when I am attempting to have an authentication performed, it results in the error mentioned above. I've tried using the token from token generator, to check if I may possibly be using the wrong token.
I am following this example to generate token for firebase auth :
https://www.firebase.com/docs/web/guide/login/custom.html
Since storing a firebase secret key is bad in terms of security, what other alternative can be followed to generate a token for authentication?
I was too stuck on this point and here's what helped me.
First things first, there are two types of users who can access database from firebase
Authorized
Non-authorized
By default it is set to non-authorized but then they do not have any permissions neither read nor write, so initially if you try to perform any operation you get the permission denied error.
So basically one has to change the required permissions on the firebase console in-order to access the database.
Complete answer here
Check the rule defined in your firebase account and also the simulator options. Description is given below in a image.

Categories

Resources