Authentication after migrating to App Services Mobile App: uid vs sid - azure

I've migrated form Azure Mobile Services to the new App Services Mobile App, and I'm using the new AMS 2.0.0-beta on the client-side.
I have two providers (currently) implemented for OAuth 2.0: Google and Twitter.
Previously, I was able to get the provider token via a claim in the principal on the server, and there would be a uid (unique id) claim that would be either "Google:123456789" or "Twitter:123456789010" (or however many alphanumerics). I believe the MobileServiceClient.UserId also exposed this as well.
Now, after I've migrated to the new App Services Mobile App (and I'm using the Preview Portal now, which for the most part is pretty awesome), there is no longer a uid claim, but rather a single sid (session id) claim, something like: "sid:ABCDEFGHIJKLMNOPQRSTUVWXYZ", no matter which provider I log in with. When I looked on the client-side at the MobileServiceClient.UserId value, it also gives this "sid" value.
The point is that previously the uid token could uniquely identify a user. Now it is the same for all users across all providers!
How can I get the provider token with the App Services Mobile App that I was previously able to get with Azure Mobile Services?
Also, could anyone point me to the source code for the Azure Mobile Services 2.0.0-beta? Is it open source? I can't seem to find it on GitHub.
Edit: Here is a screenshot of the User on the server side:

Ok, after re-reading the migration documentation for the umpteenth time, I've revisited one of my earlier steps and found it to have an invalid assumption. In the documentation, it mentions considerations for authentication, including the following block of code:
ServiceUser user = (ServiceUser) this.User;
FacebookCredentials creds = (await user.GetIdentitiesAsync()).OfType< FacebookCredentials >().FirstOrDefault();
string mobileServicesUserId = creds.Provider + ":" + creds.UserId;
Now, I was unable to find "GetIdentitiesAsync", and ServiceUser has an Identities enumerable property, so I was going with that. (It was, after all, providing very similar information as the pre-migration version of ServiceUser.) However, this method apparently gets more data than is already present in the Identities enumeration.
I still can't find GetIdentitiesAsync, but after some digging around in the the class browser, I was able to find a singular version of the extension method called GetIdentityAsync in the Microsoft.Azure.Mobile.Server.AppService.ServiceUserExtensions (it's the only method there). I tracked this down to the Microsoft.Azure.Mobile.Server.AppService namespace, added a using statement and tried out the following code:
var hmm2 = await serviceUser.GetIdentityAsync<GoogleCredentials>();
I'm leaving the variable named "hmm2" because I have the following screenshot:
The green box on the right with the numbers is the unique identifier I was getting before the migration! So to get the uid, one would need to call this extension method against all provider credentials. When the non-null credentials are found, then it can use the nameidentifier claim to get the provider's unique identifier for the user.
I am hoping that once App Services is production ready, we have a little bit more concise way of getting the non-null provider credentials, but for now this works!
Edit: Here is my code now that is working on the server-side (the client side MobileServiceClient.UserId does not work. You have to return information from the server):
var serviceUser = (Microsoft.Azure.Mobile.Server.Security.ServiceUser)Thread.CurrentPrincipal;
try
{
var googleCreds = await serviceUser.GetIdentityAsync<GoogleCredentials>();
if (googleCreds != null && googleCreds.Claims != null)
{
_CurrentProvider = "Google";
var nameClaim = googleCreds.Claims.Single(x => x.Key.Contains("nameidentifier"));
_CurrentProviderKey = nameClaim.Value;
return;
}
var twitterCreds = await serviceUser.GetIdentityAsync<TwitterCredentials>();
if (twitterCreds != null && twitterCreds.Claims != null)
{
_CurrentProvider = "Twitter";
var nameClaim = twitterCreds.Claims.Single(x => x.Key.Contains("nameidentifier"));
_CurrentProviderKey = nameClaim.Value;
return;
}
throw new NotSupportedException("The OAuth Provider is not supported.");
}
catch (Exception ex)
{
throw new InvalidOperationException("There was an error updating the authentication provider. InnerException: " + ex, ex);
}

Related

Azure Authentication Id is not stable

I am using Azure mobile app services with Xamarin Forms.
In my app, I use web social media authentication (Facebook, Twitter, Google) configured in the azure portal.
I am taking the sid gotten from CurrentClient.Id to match it with users in my Easy Tables. However, for some users, after logging in with the same account and same provider, no match is found in my database because the sid is different! I am 100% sure that it is the same account used to login before, yet I get a different sid. How is that possible? Shouldn't it remain the same with every login or what's the whole point of it then?
You are using Azure App Service Authentication for this. There is a stable ID that is available within the JWT that you pass to the service. You can easily get it from the /.auth/me endpoint (see https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-how-to#validate-tokens-from-providers )
When you GET /.auth/me with the X-ZUMO-AUTH header set to the authenticationToken returned from the login, the user.userId field will be populated with a stable ID. So, the next question is "how do I add this / compare this within the Node.js backend?" Fortunately, the HOW-TO FAQ for Node.js explicitly answers this. Short version is, use context.user.getIdentity() (an async method) to get the identity, then do something with it:
function queryContextFromUserId(context) {
return context.user.getIdentity().then((data) => {
context.query.where({ id: data.userId });
return context.execute();
});
}
function addUserIdToContext(context) {
return context.user.getIdentity().then((data) => {
context.itme.id = data.userId;
return context.execute();
});
}
table.read(queryContextFromUserId);
table.insert(addYserIdToContext);
table.update(queryContextFromUserId);
table.delete(queryContextFromUserId);
The real question here is "what is in the data block?" It's an object that contains "whatever the /.auth/me endpoint with the X-ZUMO-AUTH header produces", and that is provider dependent.
The mechanism to figure this out.
Debug your client application - when the login completes, inspect the client object for the CurrentUser and get the current token
Use Fiddler, Insomnia, or Postman to GET .../.auth/me with an X-ZUMO-AUTH header set to the current token
Repeat for each auth method you have to ensure you have the formats of each one.
You can now use these in your backend.

Get Azure AD Groups Before Building Authorization Policies

We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.

Azure. Owin OpenId authentication. Added custom claims. AuthorizationCodeReceived is not called

I've almost configured my OpenId owin authentication/authorization in Azure Active Directory. My configuration is the following:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "AppServiceAuthSession"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = _authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
RedirectUri = PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(context.AuthenticationTicket.Identity.Claims);
var appToken = "MyToken";
id.AddClaim(new Claim("MyTokenKey", appToken));
context.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties
);
}
},
});
But I want to add one more application token (not user token) to claims list to be able to have ability to use this token in any place on my site. Also it's good point for me that I don't need to get this token from my external token provider more then one time per an authentication session.
But place, where I'm going to add my logic (AuthorizationCodeReceived as well as other methods from OpenIdConnectAuthenticationNotifications) is called only when I use my local IIS(run locally), when I try to use azure IIS, this method has not been called at all. In this case my User is authenticated anyway, but this method and the similar methods from OpenIdConnectAuthenticationNotifications(except RedirectToIdentityProvider) are not fired.
I've downloaded the git source code of Katana project and referenced this project to my instead of the official nuget packages to debug its and as I think currently, I've found the reason why it happens. The AuthorizationCodeReceived "event" method is called from OpenIdConnectAuthenticationHandler class in AuthenticateCoreAsync method. But also, the calling of this method is required that the below checking must give the true result:
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
{
//some necessary preparation to call `AuthorizationCodeReceived` event method
}
As we can see, this checking allows only POST requests and I see these POST requests when I run app in local IIS, but I cannot see these POST requests when I deploy my application in azure portal (I've debugged both of options : on local IIS and in azure portal).
As summary from the above, this is the only one difference between these runnings. (Azure IIS doesn't send POST request at all by some reason).Any other methods in Katana project (which I checked) are called in the same way.
Could anybody help with it?
PS Note, I check any changes only after clearing of browser data (cache/history and so on).
The answer is the following:
The authorization in azure portal should be configured as shown above. In case if you chose LogIn with Azure Active Directory, then app services auth takes place outside of your app, and the custom authorization is not triggered.

Can I use Google APIs with MVC 5 Authentication via Google instead of using Google's OAuth 2.0?

Note, that I have previously written an MVC app according to Google Guidelines, by first setting up a project in Google Developers Console, providing Google Auth 2.0 authentication. Everything worked (not perfect though), but worked.
The Client ID and Client Secret I placed in Web.config. Therefore only one specific user could get his information. But I wanted any user to be able to get
his gmail message and calendar events, so we need this general Authentication model, like the one implemented in MVC 5 as Google Sign-in button. MVC 5 provides built-in functionality to sign in via Google, Facebook, Twitter, etc.
After you sign in via Google option, is it possible to access Google APIs, such as Calendar, Gmail, etc. to get my own events, gmail messages?
Example, I want to get Gmail message, I have this class:
public static MyGmail GetGmail(UserCredential credential)
{
var mygmail = new MyGmail();
var service = new GmailService(new BaseClientService.Initializer { HttpClientInitializer = credential });
var request = service.Users.Messages.List("me");
request.Q = "in:inbox is:unread";
var messages = request.Execute().Messages;
return mygmail;
}
Notice this line?
var service = new GmailService(new BaseClientService.Initializer { HttpClientInitializer = credential });
I need to pass credential to that service.
So I have this action in my controller:
public ActionResult Gmail()
{
return PartialView(GmailManager.GetGmail(result.Credential));
}
Notice this parameter: result.Credential?
That is the credentials.
That result object is coming from the Index Action:
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
if (result == null || result.Credential == null)
{
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null) return new RedirectResult(result.RedirectUri);
result.Credential.Token.ExpiresInSeconds = expiresInSeconds;
result.Credential.Token.RefreshToken = refreshToken;
}
return View();
}
Notice this line?
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
The result object is defined like this:
private static AuthorizationCodeWebApp.AuthResult result;
This is how Google OAuth 2.0 works. I am not putting here the AuthCallbackController code for simplicity. Who know what I am talking about and have seen Google Developers Tutorial on authenticating via Google OAuth 2.0, he will understand.
The idea is that after the Authentication (or when you already have the Auth token) you get that result object that has result.Credential property that I can pass as parameter to Google API Service (Gmail, Calendar or any other service) and the authenticated user can retrieve his own gamail messages or calendar events.
Now, to the question - since Authentication via 3-rd party providers such as Google is a part of MVC 5 functionality, the OWIN Katana, Microsoft Implementation (the Google button you can click and Authenticate), I dont need Google OAuth 2.0 code to authenticate a user.
That brings me to the problem - since there is no Google OAuth 2.0 implementation here, I will not be able to get that result object with Credentials, that I need to pass as parameter to Google API service.
But the user is Authenticated already via Google, so that should be double to call Google API Service, just dont know how?
Any ideas? Thanks
I guess not :)) Since there is no AuthorizationCodeWebApp.AuthResult object, that contains result.Credentials that is required to be passed as parameter to Google API Service:
var service = new GmailService(new BaseClientService.Initializer {
HttpClientInitializer = credential });
where credential is of type UserCredential (the one that comes back after Google OAuth2 authorization: AuthorizationCodeWebApp.AuthResult.Credential)

Getting username and group info from Azure using adal4j

I am developing a mobile app in which I need to authenticate a user against Azure AD. Basically the user will be prompted their organisational email and password, which the mobile phone app sends to the backend server which will authenticate.
I have the 'public-client-app-sample' of 'azure-activedirectory-library-for-java' working, and can authenticate against 'graph.windows.net':
private static AuthenticationResult getAccessTokenFromUserCredentials(
String username, String password) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
However, this does not return any userInfo (is null), and I can't figure out at this moment how to query to get a list with groups the user belongs to?
Do I just do manual lookups using the API using the tokens obtained from Adal4j, or is there a provided function within the library?
I am only starting with Azure, so it might be obvious, I might just be looking in the wrong places. I tried e.g. 'https://graph.windows.net/xxx.com/groups?api-version=1.5' but get 'Resource 'https://graph.windows.net/xxx.com/groups?api-version=1.5' is not registered for the account.'
First, you're absolutely right, adal4j was failing to return UserInfo. The reason for this was that the token request did not include the scope=openid parameter, which is required if the caller wants an id_token in the response. I opened an issue, and it has already been resolved. So, an updated copy of adal4j will fix your first issue of not getting UserInfo.
Now, regarding group membership for the current user: Normally, I would recommend that you simply configure you application to return the groups claim. This can be done very easily by changing the application manifest (downloaded and uploaded via the Azure portal, under the Application's configuration page) to include:
"groupMembershipClaims": "All",
Unfortunately, adal4j does not yet include the groups claim in the result of getUserInfo(), so that probably won't work much for you (issue opened, it really depends on how quickly it gets implemented, or if you want to implement youself).
Regardless, because it is possible for there to be too many groups to include in the token (indicated by , your application should always be able to use the AAD Graph API to retrieve the full set of groups the user is a member of.
And that brings me to the last point: adal4j does not implement a client of the Azure AD Graph API. So yes, you would have to implement that yourself (or perhaps use/modify an existing OData client).
(You can read more about Group Claims in this Azure AD blog post.)

Resources