How to get Cognito user pool "sub" attribute on iOS - ios

I am using a "Cognito User Pool authorizer" (no "AWS_IAM" option, no custom coded authorizer) to call Lambda methods via API Gateway and identify the user logged in on the iOS client.
On Lambda, I use the user id I get from the Cognito User Pool authorizer via event.requestContext.authorizer.claims.sub (to store the user id with some DynamoDB items).
I now need to compare this with the id of the logged in user in the iOS client.
I found [AWSIdentityManager defaultIdentityManager].identityId, but this (obviously) returns he IdentityID (which I can look up in the AWS console in Cognito --> Federated Identities --> Identity Browser), which is different from the "sub" id I see in Cognito --> User Pools --> Users and groups
Can I get the "sub" via the AWS iOS SDK?
If I cannot get it, what other id parameter should I use that I can retrieve both on Lambda and the client to identify the current client user/the user making the API request?

It seems that I have to specifically request the attributes via the user details like this:
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:AWSCognitoUserPoolsSignInProviderKey];
AWSCognitoIdentityUser *user = [pool currentUser];
NSString *mySub;
[[user getDetails] continueWithBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserGetDetailsResponse *> * _Nonnull task) {
if(!task.error){
AWSCognitoIdentityUserGetDetailsResponse *response = task.result;
NSArray<AWSCognitoIdentityProviderAttributeType*> *userAttributes = response.userAttributes;
for (AWSCognitoIdentityProviderAttributeType *attr in self.userAttributes) {
if ([attr.name isEqualToString:#"sub"]) {
mySub = attr.value;
}
}
} else {
NSLog(#"Error fetching Cognito User Attributes: %#", task.error.localizedDescription);
}
}];

Another solution (tested with the AWS JavaScript SDK):
When we authenticate with Cognito, we can retrieve a JWT token:
user.authenticateUser(authenticationDetails, {
onSuccess: (result) => resolve(result.getIdToken().getJwtToken()),
onFailure: (err) => reject(err)
})
It happens that this JWT token is an standard object that can be decoded.
Using Auth0 JWT decode (npm install jwt-decode), we can decode this token and retrieve all user attributes (e-mail, username, etc.) and the sub.
var jwtDecode = require('jwt-decode');
var decoded = jwtDecode(token);
console.log(decoded);
// prints sub, email, username, ...

Related

Azure Mobile Services LoginAsync method not working with Microsoft Auth Token

I have successfully been able to get an access_token (or authenticationToken for Microsoft tokens) using the client side authentication in my Xamarin forms App. I am able to get further user information (email, name, etc.) using the same access token. Now, when I try to pass that token to my Azure Mobile Service backend, I get a 401 error.
Here is my code:
private async System.Threading.Tasks.Task<string> MSGetUserInfo(Account account)
{
// Reference: http://graph.microsoft.io/en-us/docs/overview/call_api
// Note that Microsoft don't recognize the access_token header entry, but rely instead on an Authorization header entry
var client = new HttpClient();
var userInfoRequest = new HttpRequestMessage()
{
RequestUri = new Uri("https://graph.microsoft.com/v1.0/me"),
Method = HttpMethod.Get,
};
// Add acccess Bearer
userInfoRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", account.Properties["access_token"]);
using (var response = await client.SendAsync(userInfoRequest).ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
Models.User user = new Models.User();
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var jobject = JObject.Parse(responseString);
var userName = (string)jobject["userPrincipalName"];
// Check username is valid
if (String.IsNullOrEmpty(userName))
{
throw new Exception("Username was not set for authenticated user");
}
else
user.ProviderLoginId = userName;
var userDisplayName = (string)jobject["displayName"];
// Replace display name if invalid
if (String.IsNullOrWhiteSpace(userDisplayName))
{
userDisplayName = userName;
}
else
user.Name = userDisplayName;
var userEmail = (string)jobject["mail"];
// Replace email if invalid
if (String.IsNullOrWhiteSpace(userEmail))
{
userEmail = userName;
}
else
user.Email = userEmail;
Valufy.App.currentUser = user;
}
else
{
throw new Exception("OAuth2 request failed: " + await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
return "success";
}
The above code snippet works in getting my user details. Now when I try to use the same token in the subsequent call, I get a 404:
public async Task<bool> Authenticate(string token)
{
string message = string.Empty;
var success = false;
JObject objToken = new JObject();
//objToken.Add("access_token", token); //for facebook and google
objToken.Add("authenticationToken", token); //for microsoft
try
{
// Sign in with Facebook login using a server-managed flow.
if (user == null)
{
//ProviderAuth("MICROSOFT");
user = await syncMgr.CurrentClient
.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, objToken);
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;
}
Is there something that I am doing wrong? Any and all assistance is appreciated.
According to your description, I followed this Git sample about Microsoft Graph Connect Sample for UWP (REST). I could get the access_token and it could work as expected with Microsoft Graph API (e.g. Get a user). But when I use this access_token as the authenticationToken token object for MobileServiceClient.LoginAsync, I could also get 401 Unauthorized.
Then I checked the managed client for Azure Mobile Apps about Authenticate users. For Client-managed authentication flow, I found that the official code sample about using Microsoft Account is working with Live SDK as follows:
// Request the authentication token from the Live authentication service.
// The wl.basic scope should always be requested. Other scopes can be added
LiveLoginResult result = await liveIdClient.LoginAsync(new string[] { "wl.basic" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
session = result.Session;
// Get information about the logged-in user.
LiveConnectClient client = new LiveConnectClient(session);
LiveOperationResult meResult = await client.GetAsync("me");
// Use the Microsoft account auth token to sign in to App Service.
MobileServiceUser loginResult = await App.MobileService
.LoginWithMicrosoftAccountAsync(result.Session.AuthenticationToken);
}
Note: As LiveConnectSession states about AuthenticationToken:
The authentication token for a signed-in and connected user.
While check the authentication with Microsoft Graph, I could only find the access_token instead of AuthenticationToken.
UPDATE:
I have checked LiveLogin for WP8 and Microsoft Account Authentication for Mobile Apps via Fiddler to capture the authorize requests. I found that MS account authentication has the similar authorize request as Live SDK.
I assumed that you need to leverage Live SDK to authenticate the user when using client side authentication with Microsoft account. I found the Live SDK download page is not exist, you could follow the Live SDK for WP8 to get started with Live SDK.
UPDATE2:
For the client-flow authentication (Microsoft Account), you could leverage MobileServiceClient.LoginWithMicrosoftAccountAsync("{Live-SDK-session-authentication-token}"), also you could use LoginAsync with the token parameter of the value {"access_token":"{the_access_token}"} or {"authenticationToken":"{Live-SDK-session-authentication-token}"}. I have tested LoginAsync with the access_token from MSA and retrieve the logged info as follows:

Requests to MS Graph API gives me "Authorization Request Denied - Insufficient privileges to complete the operation"

I have a question about "Authorization Request Denied - Insufficient privileges to complete the operation" message that I keep getting back from my requests to Windows Graph API.
Specifically, I'm working in Azure cloud. I have an iOS mobile app that invokes an API.
I have turned on "Authentication for Active Directory" in my Portal.
Then, on the client side (iOS):
[self.todoService.client loginWithProvider:#"windowsazureactivedirectory"
controller:self
animated:YES
completion:^(MSUser *user, NSError *error) {
if(!error && user) {
[self refresh];
}
}]; //loginWithProvider
So returns a valid MSUser object. I see the web login controller appear, I sign in with my un/pw, and then it lets me access my Easy Table's data...etc.
Now, I want to invoke an Easy API that I've created in Azure called getUserData. Hence, I simply insert the invokeAPI code like this (iOS):
[self.todoService.client loginWithProvider:#"windowsazureactivedirectory"
controller:self
animated:YES
completion:^(MSUser *user, NSError *error) {
if(!error && user) {
//NSMutableDictionary * dict = [NSMutableDictionary dictionary];
//[dict setObject:#YES forKey:#"complete"];
NSLog(#"%s - %#", __FUNCTION__, user);
[self refresh];
[self.todoService.client invokeAPI:#"getUserData"
body:nil
HTTPMethod:#"POST"
parameters:nil
headers:nil
completion:^(id _Nullable result, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"%s - API returned response! ", __FUNCTION__);
NSLog(#"%#", result); //TODO: user info here!! :D
}]; //invokAPI
} //if user returned from AAD login is valid
}]; //loginWithProvider
Everything is fine as the API is called and I can see the response data.
On the server side (Node JS), I basically do 3 things:
1st is to get the user object id from the request object:
req.azureMobile.user.getIdentity().then((data) => {
//get user object ID
}
2nd, make a request to https://login.windows.net to get an Access Token with a username/password.
var options = {
url: "https://login.windows.net/" + tenant_domain + "/oauth2/token?api-version=1.0",
method: 'POST',
form: {
grant_type: "client_credentials",
resource: "https://graph.windows.net",
client_id: clientID,
client_secret: key
}
};
req(options, function (err, resp, body) {
//get the result back
}
I get a whole bunch of data back including the Access Token.
3rd, make a request to https://graph.windows.net/, and provide this Access Token along with my User Object ID:
var options = {
url: "https://graph.windows.net/" + tenant_domain + "/users/" + objectId + "?api-version=1.0",
method: 'GET',
headers: {
"Authorization": "Bearer " + access_token
}
};
This is so that I can User data. Now, in a separate test Subscription, I set up all the basic read permissions for AAD and Graph in my AAD management. I successfully get the user's full data back like so:
user = {
accountEnabled = 1;
assignedLicenses = (
);
assignedPlans = (
);
city = xxxxxxxxx;
country = xxxxxxxxxx;
department = Dev;
dirSyncEnabled = "<null>";
displayName = xxxxxx;
facsimileTelephoneNumber = "<null>";
givenName = hehe;
jobTitle = "iOS dev";
lastDirSyncTime = "<null>";
mail = "<null>";
mailNickname = "xxxxxxxxxx.com#EXT#";
mobile = "+xx xxx xxxx 3852";
objectId = "xxxxxxx-2c70-4aab-b261-3b2b97dc5c50";
objectType = User;
"odata.metadata" = "https://graph.windows.net/xxxxxxxxxx.onmicrosoft.com/$metadata#directoryObjects/Microsoft.WindowsAzure.ActiveDirectory.User/#Element";
"odata.type" = "Microsoft.WindowsAzure.ActiveDirectory.User";
otherMails = (
"xxxxxxxxxxxx#gmail.com"
);
...etc
}
However, in another subscription, I did the exact same steps. Even going as far as checking all the permissions like so:
I keep getting an "Authorization Request Denied, Insufficient privileges" message. The error is null so I know everything else went through correctly.
I can't figure out why because everything processes through and I checked all of my AAD and Graph permissions.
log result:
-----body------
'{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}'
Thanks for any help, and appreciate everyone's time
You can try to upgrade the role of the AD application you use to a administrator permission. Run the following commands in PowerShell:
Connect-MsolService
$ClientIdWebApp = '{your_AD_application_client_id}'
$webApp = Get-MsolServicePrincipal –AppPrincipalId $ClientIdWebApp
#use Add-MsolRoleMember to add it to "Company Administrator" role).
Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $webApp.ObjectId

When we need to call - (AWSTask *)refresh which will backend to fetch token and identity id

As the token will get expired in one hour and we need to fetch new token, I want to know will be allocating cumstomIdentityProvider class again or thers is another ay of doing that. Need help.
This is i have implemented in My cumstomIdentityProvider.
- (AWSTask *)refresh {
/*
* Get the identityId and token by making a call to your backend
*/
// Call to your backend
// Set the identity id and token
self.identityId = IdentityId;
self.token = Token;
return [AWSTask taskWithResult:self.identityId];
}

Restkit 0.20 - Change Authorization header with new token

I use Restkit in my iOS app to call an API.
When the app first launched, a client token is retrieved from the API in order to call the service to post a new user.
After this user has successfully being created, a new access token is sent back by the API. This time is it is a user token.
All the other requests to the API made by the app will now have to use this user token.
I am using a singleton class that inherits from RKObjectManager. I then built one class per ressource to access (example : Users, Images, ...) all inheriting from that main class called AKObjectManager.
In AKObjectManager I have the following method :
+ (instancetype)sharedManager
{
NSURL *url = [NSURL URLWithString:LLY_API_BASE_URL];
AKObjectManager *sharedManager = [self managerWithBaseURL:url];
sharedManager.requestSerializationMIMEType = RKMIMETypeJSON;
...
// Access Token
NSUserDefaults* userData = [NSUserDefaults standardUserDefaults];
if ([userData objectForKey:#"accessToken"]) {
// Not too sure if this is being taken into account for the other class that inherits
[ sharedManager.HTTPClient setDefaultHeader:#"Authorization" value:[userData objectForKey:#"accessToken"]];
}
return sharedManager;
}
I thought that by checking for every access the accessToken in NSUserDefaults and setting it in the Authorization field in the header would work but no. I can see through NSLog that the new access token is set when changing it for for some reasons using Charles the header of the request still points to the old one.
I then used
[[AKObjectManager sharedManager].HTTPClient setDefaultHeader:#"Authorization" value:accessToken.accessToken];
As soon as I got the new token but faced the same issue.
Finally I went for that road (UserManager inherits from AKObjectManager)
// Force the newly refresh token to be set in the Authorization header
[[UserManager sharedManager].HTTPClient setDefaultHeader:#"Authorization" value:[[NSUserDefaults standardUserDefaults] objectForKey:#"accessToken"]];
[[UserManager sharedManager] show:nil // userId equals nil meaning it will be replaced by 'self'
success:^(User* user){
self.user = user;
...
And it worked but I am not too happy about the implementation.
Could you point me to where I got it wrong and advise on how to do it ?

Meteor acounts add info from Twitter accounts

I'm trying to figure out how I can add additional information from a user's Twitter account to the created account on a Meteor installation.
In particular I am trying to access the user's bio via Twitter Api v 1.1 and am not successful in doing so.
Therefore I am trying to extend Accounts.onCreateUser(function(options,user) {}); with the Twitter bio. How do I do that? And then access this data from a template?
Here's a perfect answer for returning data from Github, however I've had trouble porting this approach over to Twitter as the authenticating service: Meteor login with external service: how to get profile information?
You could do it on this way:
Accounts.onCreateUser(function (options, user){
user.profile = options.profile || {};
//Twitter returns some useful info as the username and the picture
if(user.services.twitter){
user.profile.picture= user.services.twitter.profile_image_url_https;
user.profile.username= user.services.twitter.screenName;
}
return user;
});
For getting the data from the Twitter API I´m using the node package oauth:
OAuth = Npm.require('oauth');
oauth = new OAuth.OAuth(
'https://api.twitter.com/oauth/request_token',
'https://api.twitter.com/oauth/access_token',
'consumerKey',
'secretKey',
'1.0A',
null,
'HMAC-SHA1'
);
getTwitterUserData: function (id) {
var accountUser = AccountsUserCollection.findOne({_id: id});
var url = "https://api.twitter.com/1.1/users/show.json?screen_name="+accountUser.screen_name;
oauth.get(url, 'accessToken', 'accessSecret', function (err, data, response) {
if(err){
console.log(err);
}
if(data){
Fiber(function () {
AccountsUserCollection.update({_id: accountUser._id}, {$set: {dataTwitter: JSON.parse(data)}});
}).run();
}
if(response){
Log.info(response);
}
});
}

Resources