Thread.CurrentPrincipal has null details on Azure - c#

We are trying to use Thred.CurrentPrincipal to get the user information. However as soon as we deploy it to Azure the CurrentPrincipal is null.
var td = new TokenData();
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (claimsPrincipal != null)
{
td.IsAuthenticated = claimsPrincipal.Identity.IsAuthenticated;
td.Name = claimsPrincipal.FindFirst(ClaimTypes.Name).Value;
td.Email = claimsPrincipal.FindFirst(ClaimTypes.Upn).Value;
td.Surname = claimsPrincipal.FindFirst(ClaimTypes.Surname).Value;
td.Role = claimsPrincipal.FindAll("http://schemas.xmlsoap.org/claims/Group")
.Select(s=>s.Value);
}
I verified that ADFS is set-up correctly. It would be great if someone could point me to the pain-point of this.
Edit:
The CurrentPrincipal is not NULL but the details are. So we are not getting the user's name, roles and other details.
Edit 2:
This is what is happening on Azure:

You might need to turn on User Profiles in order to get your principal to work.
To turn it on In the portal on your website go to Configuration -> App Settings and add the app setting WEBSITE_LOAD_USER_PROFILE = 1. You site needs to be in Basic or Standard mode for this to work.

The problem is that the Claim Rule wasn't set-up for the ADFS record.
http://www.cloudidentity.com/blog/2014/02/12/use-the-on-premises-organizational-authentication-option-adfs-with-asp-net-in-visual-studio-2013/

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?

Authentication against local AD in the Angular application

I've been developing an Angular app with .NET Core backend (services). The task is to enable an integrated authentication, i.e. make it work with the local user seamlessly, so I login to my (connected to a local AD) machine once and the web application lets me in without the necessity to login a second time. We've been working with Identity Server 4 and intended to implement this scenario using it.
There is a little documentation on the official website concerning the Windows Authentication (e.g. against Active directory): http://docs.identityserver.io/en/latest/topics/windows.html but it doesn't explain much. As per my info, to make this scenario work the browser utilizes either Kerberos or NTLM. Neither of them is mentioned in the IS4 docs. I'm lacking the understanding of how the local credentials are getting picked up and how IS4 'knows' the user belongs to AD? How I can make sure only the users from a specific domain have access to my app?
I found some working stuff here https://github.com/damienbod/AspNetCoreWindowsAuth but questions remain the same. Even though I was able to get to the app with my local account I don't understand the flow.
I expect the user utilizing the app in the local network to log-in to the app without entering the login/password (once he's already logged in to the Windows). Is this something achievable?
Identity Server is intended to serve as an Identity Provider, if you need to talk with your AD you should see the Federation Gateway architecture they propose using the IAuthenticationSchemeProvider. Where Identity Server acts as an endpoint and talks with your AD.
This is the link:
http://docs.identityserver.io/en/latest/topics/federation_gateway.html
You have the control to programmatically reach your AD and pass the correct credentials to get the authentication. That step should be done in your Identity Server. After you get authenticated you should get redirected to your application again.
About your last question, the answer is yes, if you have your website hosted on an intranet and you have the access to your AD, you don't need to capture your credentials as user input, you can programmatically reach the AD as I said.
Bellow is the code I use to connect with my active directory
On the ExternalController class, you get when you use IdentityServer, you have this:(I don't remember at the top of my head how much I changed from the original code, but you should get the idea)
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public async Task<IActionResult> Challenge(string provider, string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
// validate returnUrl - either it is a valid OIDC URL or back to a local page
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
// windows authentication needs special handling
return await ProcessWindowsLoginAsync(returnUrl);
}
else
{
// start challenge and roundtrip the return URL and scheme
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", provider },
}
};
return Challenge(props, provider);
}
}
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, testing windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
If you want to use Azure AD, I would recommend you to read this article:
https://damienbod.com/2019/05/17/updating-microsoft-account-logins-in-asp-net-core-with-openid-connect-and-azure-active-directory/
Not sure if it's what you want, but I would use the Active Directory Federation Services to configure an OAuth2 endpoint and obtain the user token in the .Net Core Web App.
Isn't NTLM authentication support limited on non Microsoft browsers?
OAuth2 have the advantage of using only standard technologies.
One way to do it is to have 2 instances of the app deployed.
The first one is configured to use Windows Authentication and the other one uses IS4.
ex:
yoursite.internal.com
yoursite.com
Your local DNS should redirect traffic internally from yoursite.com to yoursite.internal.com
yoursite.internal.com will be the one configured to use AD authentication. You should have a flag in your appsettings.json to indicate if this instance is a AD auth or IS4 auth.
The downside of this solution is that you have to deploy 2 instances

Force ASP.Net claims validation after they have been changed

I'm developing an ASP.Net application with OWIN and currently I have a problem with claims. I have two levels of application: basic and advanced. Some features are available for advanced users only. So I check claims, and if user doesn't have claim advanced I return 403. But here I found the workaround which ruins this system:
User activates advanced mode
He performs any action and save its access token
He disactivates advanced mode
Now he's able to perform actions just like he is in advanced mode with this token, however he actually has not permissions to do it.
I'm trying to find some fine solution for this situation but I have no ideas except set 1 minute timeout or always check AspNetUserClaims instead of cookie and so on, but they don't work in my case because he can activate a lifetime feature in this one minute interval and then use it forever.
But i'd like to set some server-side flag like oops, this guy have just changed his cookies, check it from database or something to lower database roundtrips for common API calls.
Is there any standard default way to do it? Or maybe I have just chosen a wrong instrument?
You Need to send update cookies according to your claim value.
Below is code to update your claim value.
Inside your action when user disable/enable advanced mode, Then update user claims.
var isAdvanced= "1";
var identity = (ClaimsIdentity)User.Identity;
// check if claim exist or not.
var existingClaim = identity.FindFirst("IsAdvanced");
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add/update claim value.
identity.AddClaim(new Claim("IsAdvanced", isAdvanced));
IOwinContext context = Request.GetOwinContext();
var authenticationContext = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
if (authenticationContext != null)
{
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity,authenticationContext.Properties);
}
As soon as you will made a redirection, you will get your get updated claim value, hence you don't need to make database round trip.
Credit to this post.
Unfortunly, the only way I found is actually query DB itself and check if user has valid credentials:
public bool HasRequiredClaims(string[] requiredClaims)
{
using (var context = new ApplicationDbContext())
{
int actualNumberOfClaims = context.Users
.SelectMany(x => x.Claims)
.Count(c => requiredClaims.Contains(c.ClaimValue)); // claim values are unique per user (in my case) so I don't have to filter on user
return actualNumberOfClaims == claimsValuesToSearch.Length;
}
}

OWIN MVC - Multiple LoginProviders, Current LoginProvider

I have multiple login providers available. I can login with external account or using forms auth. Everything works fine.
I'am redirecting user to HomePage, and now i would like to know which login provider was used.
Is there is a possibility to find out in controller, which loginprovider was used?
Thanks for help!
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
string provider = loginInfo.Login.LoginProvider; // Facebook, Google, Twitter, Microsoft...
I got this working although it's not something that would work in all cases.
Here's the code I used in a HomeController function:
// get the provider name
var authCtx = HttpContext.GetOwinContext();
var usrMgr = authCtx.GetUserManager<ApplicationUserManager>();
var user = usrMgr.FindByName(HttpContext.User.Identity.Name);
var extLoginInfo = usrMgr.GetLogins(user.Id).FirstOrDefault();
var loginProvider = extLoginInfo.LoginProvider.ToLower();
The main issue here is that I'm not getting the actual login provider that the user is currently logged-in with. I'm just getting the first login provider associated with this user (which in my case happens to be the same, so it works).
Would be great to find a way to get the actual provider that the user is currently logged-in with.

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

Categories