How can I uniquely identify Active Directory guest users in a Blazor/ASP application - c#

I am using a Blazor application with Azure Active Directory authentication and guest users. When a user logs in, I want to use their authenticated identity as a key to lookup permissions and other attributes in my own database. Initially I used this code...
#using Microsoft.AspNetCore.Components.Authorization
#inject AuthenticationStateProvider AuthenticationStateProvider
#code {
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
//Don't do this: Name != email and may not even be constant
var email = user.Identity.Name;
var attributes = await myDatabase.FetchAttributesForUser(email);
//use attributes....
}
}
However, user.Identity.Name is not the email address of the user. As described in this old post, it is simply an identifier that the authentication provider supplies. For example, a user with an outlook address of first.last#outlook.com might be authenticated with a Name of live#first.last#outlook.com There's therefore no guarantee that the Name may be unique across providers or even across time for the same provider.
This stackoverflow question is identifying the same problem and the accepted answer is to use the SID but what is missing is an explanation of how I can retrieve the SID from the AuthenticationStateProvider that's been injected into my application. (There are no obvious fields or paths that lead to a SID.) Should I be injecting some other authentication object?
Also, if the recommendation is to use the SID, how can I obtain it for pre-provisioning? I.e. I invite "jo.bloggs#contoso.com" and want to set up attributes in my database before she first logs in. Is the "object id" shown in the portal actually the SID?

In Azure AD you can use either the oid or sub claim.
The oid claim contains the object id for the user in the Azure AD tenant they signed into.
It is thus unique across applications.
The sub claim is unique for that user in one application.
Both of them are immutable.
A .NET ClaimsPrincipal may contain the oid claim with a different name: http://schemas.microsoft.com/identity/claims/objectidentifier.
One source for these alternate names is the System.Security.Claims.ClaimTypes class.
Claims in id tokens: https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens#payload-claims

A general solution for obtaining the SID from the AuthenticationStateProvider with respect to the below in point asked in the original question.
This stackoverflow question is identifying the same problem and the accepted answer is to use the SID but what is missing is an explanation of how I can retrieve the SID from the AuthenticationStateProvider that's been injected into my application. (There are no obvious fields or paths that lead to a SID.) Should I be injecting some other authentication object?
If you notice, the AuthenticationState's User property returns the Identity property of type IIDentity which has the base functionalities for WindowsIdentity and other Identities as well. Hence converting the Identity into the WindowsIdentity becomes valid here. And after converting you can get the SID information of the user as below. The other information of the user can be obtained directly from the identity object.
var authenticationState = await authenticationStateTask;
WindowsIdentity identity = authenticationState.User.Identity as WindowsIdentity;
var SId = identity.User.Value;
Hope it helps.

Related

Prompt user for additional information during an Open Id Connect event?

Using asp.net Core, Mvc and OpenIdConnect, is it possible to prompt an authenticated user for additional information during the ODIC authentication process, and then redirect back to the originally-desired page?
To give a concrete example: in our system one person, represented by an email address, can have multiple user ids that they may wish to operate under. Assume my email address is tregan#domain.com, and I have 3 user ids to choose from: treganCat, treganDog, treganMouse. When I hit a Controller action that is decorated with the [Authorize] attribute I first go through OpenIdConnect authentication, and one of the claims returned is an email address.
Using that email address, I want the application to prompt me to select the identity that I want to run under (treganDog, treganCat, or treganMouse).
From there, I want the application to take the user id that I selected, interrogate a database for the roles that go along with the selected user id, and load those roles as claims to my identity.
Finally, I want the application to send me on to my desired page (which is the protected Controller method that I originally attempted to visit).
Is this possible?
I'm using an Owin Startup class; the code below "works" except for the fictional line "var identityGuid = [return value from the prompt];" ("fictional" because it represents what I would like to occur, but in fact a series of redirects would be needed).
My example below uses the OnTicketReceived event, but that selection is arbitrary, I would be willing to do this in any event.
services.AddAuthentication(authenticationOptions =>
{
authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(openIdConnectOptions =>
{
openIdConnectOptions.Authority = Configuration["PingOne:Authority"];
openIdConnectOptions.CallbackPath = "/Callback";
openIdConnectOptions.ClientId = Configuration["PingOne:ClientId"];
openIdConnectOptions.ClientSecret = Configuration["PingOne:ClientSecret"];
openIdConnectOptions.ResponseType = "code";
openIdConnectOptions.Events.OnTicketReceived = (ticketReceivedContext) =>
{
var emailClaim =
ticketReceivedContext.Principal.Claims.FirstOrDefault(o =>
o.Type == ClaimTypes.Email);
string emailAddress = emailClaim.Value;
//here is where I would like to prompt the user to select an identity based on the email address
//the selected identity is represented by a guid
var identityGuid = [return value from the prompt];
var roles = new MyRepository(myContext).GetRolesForUserId(identityGuid);
var claims = new List<Claim>();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
ticketReceivedContext.Principal.AddIdentity(new ClaimsIdentity(claims));
return Task.CompletedTask;
};
});
This is impersonation where there is a real user and you need to identify the impersonated user after login.
You will need to complete the login first, return to the app and configure the principal. Then render a UI and receive the selected choice.
You then need your UI to call the back end and tell it to update claims in the auth cookie. Not sure if you'll get this to work though - the impersonated user may need separate storage - such as a second cookie.
This highlights that it can be useful to separate the token / credential the UI receives from the claims the back end works with.
I use the below design a lot for REST APIs that serve UIs directly - though it may be overkill for your solution:
https://authguidance.com/2017/10/03/api-tokens-claims/
I think what I want to do is simply not possible without either figuring out a way to do it inside PingOne or writing my own IdentityServer and taking care of the extra steps there.
I decided to instead write a custom middleware that fires after the Authentication middleware, as described in this SO question: In asp.net core, why is await context.ChallengeAsync() not working as expected?

how to get claims of another user using ASP.NET Core

I'm still learning identities with asp.net core. I'm doing a claims-based token authorization.
Most examples are about "Current" logged in user. In my case my RPC service is receiving a username & password of some user in the identity DB. I need to
verify that a user with such credentials exist
get all the claims of that user
so to verify if the user exists, I'm using this:
ApplicationUser applicationUser = await _userManager.FindByNameAsync(username);
bool exist = await _userManager.CheckPasswordAsync(applicationUser, password);
if (!exist)
{
// log and return
}
I don't know how to do the 2nd step properly. I guess I could do a simple linq to collect all user's claims, but I'm sure there is a better way using the identity methods.
You need to use the GetClaimsAsync() method. For example:
var claims = await _userManager.GetClaimsAsync(applicationUser);
See MSDN

How to get user name, email, etc. from MobileServiceUser?

After a lot of digging around I've got my WPF application signing users in via Azure Mobile Service. My Mobile Service is connected to an Azure Active Directory that I have set up. However, when I log the user in with MobileServiceClient.LoginAsync(...) the MobileServiceUser UserId is in an unreadable hash it seems. For example it looks like: "Aad:X3pvh6mmo2AgTyHdCA3Hwn6uBy91rXXXXXXXXXX". What exactly is this?
I'd like to grab the user's display name to use but I can't figure out how.
That is the userID of Azure Active Directory. You need to create a service to expose your AAD info through a service and retrieve the additional information using the access token you get from your user.
First:
ServiceUser user = this.User as ServiceUser;
var identities = await user.GetIdentitiesAsync();
var aad = identities.OfType<AzureActiveDirectoryCredentials>().FirstOrDefault();
var aadAccessToken = aad.AccessToken;
var aadObjectId = aad.ObjectId;
This will give you the access token and objectID , then you need to query the information through AAD graphy API.
https://msdn.microsoft.com/library/azure/dn151678.aspx
Look at the sample request part. You should provide the query with the access token you got and objectId.
Here is an alternative approach, after reading http://justazure.com/azure-active-directory-part-2-building-web-applications-azure-ad/ scroll to the section on Identity in .Net it talks how claims are a standard part of the framework. So once you get the credentials object like provided by #beast
var aad = identities.OfType<AzureActiveDirectoryCredentials>().FirstOrDefault();
You can actually grab a dictionary with the various properties. Examples of some the properties can be found at https://msdn.microsoft.com/en-us/library/system.identitymodel.claims.claimtypes(v=vs.110).aspx
So from there you can do the following:
if (aad != null)
{
var d = aad.Claims;
var email = d[ClaimTypes.Email];
}
I did this to pull the user id which was cross referenced in a table. FYI I am using App Service, but I believe the credentials object is the same in Mobile Service

Finding out the current Identity Provider in Windows Identity Foundation

I have a web site which allows multiple ways of authenticating a user (e.g. facebook, twitter, windows etc.) Each of this provides a different identity name when the user logs in. I need to find out who provided the identity (ClaimsIdentity) and accordingly select the unique id for the user to add application specific claims to the users claims set.
I created a table to associate all the entities with users primary profile table. This table contains name of the identity provider, unique id provided by the identity provider and unique user id from the profile table.
My question is how can I find the name of the identity provider when the user signs into my site using these logins? The problem is if the user has same email address used for both facebook and twitter, I am not able to find out that information in the incoming principal as used in the authentication manager's authenticate method.
You typically use the Issuer and OriginalIssuer properties in each claim that you get.
If you use e-mail as the unique identifier:
var u = this.User as IClaimsPrincipal;
var c = (u.Identity as IClaimsIdentity)
.Claims
.First( c => c.ClaimType == ClaimTypes.Email );
var issuer = c.Issuer;
var originalIssuer = c.OriginalIssuer;

Active Directory Group Membership Checking in .Net 4.5

I have an ASP.Net MVC application using Windows Authentication, and I am checking group membership for security on controller actions.
Simple as it sounds, I've found no other Question that can resolve the problem I am experiencing.
First Attempt: [Authorize]
The classic method is to simply slap an Authorize data annotation attribute on the controller action and go to town:
[Authorize(Roles = #"domain\groupName1")]
No dice. I am prompted for credentials. Usually this means something is wrong with the Windows Authentication configuration but it's setup fine: (1) HttpContext.User is a WindowsPrincipal object, and (2) I confirmed another known group name works.
Second Attempt: IsInRole()
The next step taken was to go a more old fashioned route and use IPrincipal.IsInRole(), and again, one returns false, the other true.
var wp = (WindowsPrincipal)User;
// false
var inGroup1 = wp.IsInRole(#"domain\groupName1");
// true
var inGroup2 = wp.IsInRole(#"domain\groupName2");
Stumped... so I hit up my systems nerds and we double check everything. User is a group member? Yes. Group name is spelled correctly? Yes. The next step was to snag the SID.
Third Attempt: Search Identity's Group Collection
In my controller I check the WindowsIdentity and look through the group collection for the SID of the troublesome group:
var wi = (WindowsIdentity)wp.Identity;
var group = wi.Groups.SingleOrDefault(g => g.Value == "group1-sidValue");
The group variable is the SecurityIdentifier object. Because it is not null, we can be certain that this current user is a member of the group that both the [Authorize()] or IsInRole() attempts fail to confirm.
Fourth Attempt: DirectoryServices.AccountManagement
At this point, I'm going nuts and add reference to the AccountManagement APIs. I search the domain context for the GroupPrincipal by both name and SID:
var pc = new PrincipalContext(ContextType.Domain, "domain");
var gp1byName = GroupPrincipal.FindByIdentity(pc, "groupName1")
var gp1bySid = GroupPrincipal.FindByIdentity(pc, IdentityType.Sid, "group1-sidValue");
Both group principal variables are ripe with the same object, and I verified through a watch variable that the principal's Members collection contains a UserPrincipal object with the same SID as the current WindowsPrincipal on HttpContext.
Question:
What in the hell have I missed here? Why would both role checking methodologies fail when it is plain and clear through object exploration that the user is a valid member of this given group?
The fact that one group checks fine and the other does not seems the most strange part at this point.
Answer:
Essentially it's translation issues between WindowsIdentity and NTAccount (both of these System.Security.Principal) and lastly, the actual Active Directory entry.
When validating a WindowsIdentity against AD, if you want to use anything other than the Sam or the Sid, you will need to use System.DirectoryServices.AccountManagement.
Caveat: In .Net 4.5 the security principals include Claims but that's out of context.
Long Explanation:
In a Windows Authenticated web application, HttpContext.User is a WindowsPrincipal object wrapping an underlying WindowsIdentity.
WindowsIdentity has for most intents and purposes only two properties with which the authenticated user can be identified: Name and User.
These properties translate to two properties on the identity's corresponding AD account entry:
WindowsIdentity.Name = SamAccountName
WindowsIdentity.User = SID
The [Authorize] filter attribute ultimately calls IsInRole(string role) on the underlying principal... and the IsInRole() string overload instantiates an NTAccount with the role (the "SamAccountName" in an AD entry).
This explains the failure in #1 and #2 above.
To authorize the HttpContext.User against anything but his/her Sid or SamAccountName, you'll need DirectoryServices.AccountManagement or classic LDAP.
I have an ASP.Net MVC application using Windows Authentication, and I am checking group membership for security on controller actions. Simple as it sounds, I've found no other Question that can resolve the problem I am experiencing.
Took me a lot of time to find something
http://www.c-sharpcorner.com/uploadfile/scottlysle/test-for-user-group-membership-in-Asp-Net-C-Sharp/
My code to check if user belongs to an AD group :
foreach (System.Security.Principal.IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
if (String.Equals(group.Translate(typeof(System.Security.Principal.NTAccount)).ToString(), #"your_domain_name\your_group_name", StringComparison.InvariantCultureIgnoreCase))
{
// the user belongs to a group
}
}
That is all I needed beside <authentication mode="Windows"/> in Web.config file

Categories