.NET CORE and ADFS no Claims Available - asp.net-core

Migrating to .NET Core 3 from a 4.6 project and I'm not 100% sure I am implementing things properly.
I followed the steps in this article, making sure to configure startup.cs following the code sample under the "Use WS-Federation without ASP.NET Core Identity" section.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/ws-federation?view=aspnetcore-3.0
Login seems to be working in that I'm redirected to MS login and sent back to my app with the expected cookies, for example MSISAuthenticated. But, user.identity.isauthenticated is always false and I have no claims available. Is this expected behavior? Perhaps I'm not configuring things properly? Ideally I'd like to be able to check if a user is authenticated and access the claims.
I've come across a number of articles about adding policies based on groups, but how would [Authorize (Policy="SomeGroup")] even work if no claims are available?
ConfigureServices Code:
enter image description here
Configure Code:
enter image description here
Controller Action:
public IActionResult Index()
{
var identity = (ClaimsIdentity)User.Identity;
ViewBag.Claims = identity.Claims;
return View();
}
View Code:
#using System.Security.Claims;
#{
ViewBag.Title = "Home Page";
IEnumerable<Claim> claims = (IEnumerable<Claim>)ViewBag.Claims;
}
#if (User.Identity.IsAuthenticated)
{
<div class="jumbotron">
<h1>Successful Sign On!</h1>
</div>
<div class="row">
<div class="col-md-12">
<h2>WS Federation Services Claims</h2>
#foreach (Claim claim in claims)
{
<p>
<b>#(claim.Type.ToString())</b>
<br />
#(claim.Value.ToString()) (type: #(claim.ValueType.ToString()))
<hr />
</p>
}
</div>
</div>
}
else
{
<div class="jumbotron">
<h1>SSO Test</h1>
<p class="lead">To sign in using Microsoft's single sign-on service, click the button below.</p>
<p>Sign in ยป</p>
</div>
}

perhaps the fact is that you are not send the desired ResourceUrl to ADFS. Then ADFS considers the default resource and issues a token without claims.
See more info on 3 step in "High level AD FS authentication flow"
enter link description here
AD FS identifies the resource which the client wants to access through
the resource parameter passed in the auth request. If using MSAL
client library, then resource parameter is not sent. Instead the
resource url is sent as a part of the scope parameter: scope =
[resource url]//[scope values e.g., openid].

Related

Blazor Server auth with jwt

I have been trying for a few days to do client-side persistence token authorization. The problem is that we essentially cannot use IJSRunrime when a user request comes in to check his authentication, because ServerRendering does not run before initialization on the client. I also tried to use cookies in order to save them in the request, but we cannot do this either due to the fact that the request has already been started, for example, in the same MVC, we can do this without problems.
Detailed errors:
IJSRunrime - "JavaScript interop calls cannot be issued at this time"
Cookie - "headers are read-only, response has already started"
I create this code for example and my question - How save token on the client side?
UPD: I don't want to reload SPA application, some examples do it and I can't use HttpContext.SignIn because have Cookie error.
#page "/SignIn"
<EditForm EditContext="#editContext" OnValidSubmit="SendRequestSubmit" class="auth__form">
<DataAnnotationsValidator />
<div class="auth__item">
<h4 class="auth__actions-title actions-title">Email<span>*</span></h4>
<InputText #bind-Value="signInRequest.Email" class="input-actions" />
</div>
<div class="auth__item">
<h4 class="auth__actions-title actions-title">Password<span>*</span></h4>
<InputText #bind-Value="signInRequest.Password" type="password" class="input-actions" />
</div>
<button class="auth__confirm actions-btn btn" disabled="#sendRequestLoading">
Sign In
</button>
</EditForm>
#code {
// it is code for example
private async void SendRequestSubmit()
{
var auth = accountSerrvice.auth(Email, Password);
if(auth == true && auth.Token != null)
{
// Save auth.Token in the client side
}
else
{
throw new Exception("Test error");
}
}
}

ASP.NET Core - use external provider but *keep* Default Identity database

I want the database that comes with the Default Identity provider in ASP.NET Core. However, I'd like users to login exclusively with their Microsoft account.
So at the moment, I have this in my user LoginDisplay.razor file:
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity.Name!
Log out
</Authorized>
<NotAuthorized>
Register
Log in
</NotAuthorized>
</AuthorizeView>
When the user clicks "Log in" they're taken to the regular login form:
Here they can click on the "Microsoft Account" button. What I would like to do is skip the default login screen and go directly to the Microsoft Account workflow.
How would I do that?
Keeping the identity database offers me a couple of benefits:
I plan to add more data to the database - so it's handy if I can refer to accounts that exist in the same database
It's possible that I may need to give users access to the site that do not have a Microsoft account
Update
Based on feedback, I've implemented the following:
#inject Data.Services.AntiForgery antiforgery;
<form id="external-account" method="post" class="inline-block form-horizontal" action="/Identity/Account/ExternalLogin?returnUrl=%2F">
<button type="submit" name="provider" value="microsoft" title="Log in using your Microsoft Account account">Login</button>
<input name="__RequestVerificationToken" type="hidden" value="#antiforgery.Generate()">
</form>
And here's my utility class that I used to work around the anti-forgery request token (in Blazor):
public class AntiForgery
{
public IAntiforgery Antiforgery { get; private set; }
public IHttpContextAccessor Accessor { get; private set; }
public AntiForgery( IAntiforgery antiforgery, IHttpContextAccessor accessor )
{
Antiforgery = antiforgery;
Accessor = accessor;
}
public string Generate()
{
// Code stolen from:
// * https://stackoverflow.com/questions/45254196/asp-net-core-mvc-anti-forgery; and
// * https://stackoverflow.com/questions/53817373/how-do-i-access-httpcontext-in-server-side-blazor
return Antiforgery.GetAndStoreTokens( Accessor.HttpContext ).RequestToken;
}
}
For the utility class to work, the following was added to my Startup file:
services.AddSingleton<AntiForgery>();
services.AddHttpContextAccessor();
Well, you can simply just hide the login form itself, showing only the Microsoft Account button. However, it is not possible to send the user directly into that flow. It requires an initial post, which is going to require action on the user's part, i.e. clicking the button.
For what it's worth. If you have a "Login" type link, you can code this in the same way as the Microsoft Account button, so that it then initiates the flow when the user clicks "Login". However, you'll still need an actual login page to redirect to for authorization failures, and that would still require an explicit button press there.
You could directly pass the provider name Microsoft to your external login function using asp-route-provider.
For asp.net core 2.2+, Identity is scaffolded into identity area with Razor Pages.
1.Login link.
<a asp-area="Identity" asp-page="/Account/ExternalLogin" asp-page-handler="TestExternal" asp-route-provider="Microsoft">Log in</a>
2.ExternalLogin.cshtml.cs
public IActionResult OnGetTestExternalAsync(string provider, string returnUrl = null)
{
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
3.Startup.cs
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
//use your own Id and secret
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ClientId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
});

NullReferenceException when accessing #User.Identity.Name

We are running an ASP net core 2.1 website. In the header of the page, we want to display the name of the current user. The variable is retrieved through the windows authentication scheme using the #User.Identity.Name reference and is assembled like this: ZH_MB\rohzeh, where ZH_MB is the domain and rohzeh the windows AD user. Because we just want the username, we split both loike this:
<p class="pull-right navbar-text"><span class="glyphicon glyphicon-user"></span> #User.Identity.Name.Split("\\")[1]</p>
Now when we run this code, we get the following error:
An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an
object. AspNetCore.Views_Shared__Layout.b__46_1() in
_Layout.cshtml, line 111
Line 111 is the code above. When I run it without the split part like this:
<p class="pull-right navbar-text"><span class="glyphicon glyphicon-user"></span> #User.Identity.Name</p>
It works just fine, except for the domain information that I don't want.
When i surround this code with a try/catch block, it works as expected, showing only the user name:
#try
{
<p class="pull-right navbar-text"><span class="glyphicon glyphicon-user"></span> #User.Identity.Name.Split("\\")[1]</p>
}
catch (NullReferenceException) { }
Any idea what the problem might be? My initial idea was: the information is just not ready when the page is rendered. But in this case, the second line of could should provide the same error.
It seems
your user is not authenticated yet or Name is null
or you didn't install ASP.NET Core/.NET Core: Runtime & Hosting Bundle for IIS
or you didn't configure all needed to use Windows Authentication
or forwarding Windows Authentication is set to false for some reasons
You can set it this way or others:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<IISOptions>(options => options.ForwardWindowsAuthentication = true);
// Add framework services.
services.AddMvc();
}
Thanks, the answer of Chris Pratt did the trick. I could replace the try/catch with the "?" and the username is shown corrently.

Identity.IsAuthenticated always true on azure

I'm currently working on a website with the ASP.net MVC framework for my own amusement and I decided
to stop working with a local database and to publish my web application on azure with it's associated database.
Now I have a strange issue with authentication. Identity.IsAuthenticated is always true.
At first I got the following error :
A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier.
Without understanding too much I found a solution which was to configure the AntiForgery token in the global.cs file
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
At the point the error stopped showing but the user identified don't exist in the AspNetUsers table. Of course when I try to get info from the user in a section where authentification is needed the application crashes since no entry exists.
On my layout, I have a section of code to display some data if the user is authenticated
#if (User.Identity.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
}
}
The User.Identity.GetUserName() always returns live.com#[MyAzureAccount]#outlook.com and it's impossible for me to logoff.
This seems like an identity issue but as I didn't touch the code generated when I started the project and as it works in local I would've expected it to work.
The solution was simply to download the publishing files from azure instead of filling by hand the fields for publishing. I don't really know why it worked but it solved the problem.

Supporting Individual User Accounts AND Organizational Accounts in MVC5 / ASP.Net Identity 2

I've created an ASP.Net MVC5 application, in which I have configured (and have working fine) Individual User Accounts via Google, Facebook, etc.
What I'd like to do is also support authentication against Azure Active Directory (Organizational Accounts). This would be for internal staff to be able to logon to the app as administrators.
All existing information/guides/documentation I've found typically deals with using one or the other. How would I enable them both together?
If there needs to be a separate logon form for each type of user, that would not be an issue.
EDIT:
I was looking at the Application configuration within Azure Active Directory portal, and notice that they define an "OAUTH 2.0 AUTHORIZATION ENDPOINT". Can MVC5 be configured within Startup.Auth.cs to use this?
I managed to implement this by doing the following:
First, adding a reference to the Microsoft.Owin.Security.OpenIdConnect Nuget package.
Second, configuring it in my Startup.Auth.cs:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "From the Azure Portal (see below)",
Authority = "https://login.windows.net/<domain>.onmicrosoft.com",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (ctx) =>
{
if (ctx.Request.Path.Value.EndsWith("ExternalLogin"))
{
string appBasePathUrl = ctx.Request.Scheme + "://" + ctx.Request.Host + ctx.Request.PathBase;
ctx.ProtocolMessage.RedirectUri = appBasePathUrl + "/";
ctx.ProtocolMessage.PostLogoutRedirectUri = appBasePathUrl;
}
else
{
ctx.State = NotificationResultState.Skipped;
ctx.HandleResponse();
}
return Task.FromResult(0);
}
},
Description = new AuthenticationDescription
{
AuthenticationType = "OpenIdConnect",
Caption = "SomeNameHere"
}
});
Third, I setup the application in the Azure Portal (classic):
Fourth, I added a separate logon page for admin users:
#using (Html.BeginForm("ExternalLogin", "Home"))
{
#Html.AntiForgeryToken()
<div class="ui basic segment">
<div class="ui list">
<div class="item">
<button type="submit" name="provider" value="OpenIdConnect" class="left floated huge ui button social">
<i class="windows icon"></i>
<span>My Org Name</span>
</button>
</div>
</div>
</div>
}
Fifth, the ExternalLogin action doesn't need to change - we just let OWIN middleware redirect us to the external login page. The flow would then direct the user back to the ExternalLoginCallback action.
Finally, in the ExternalLoginCallback action, I check the incoming claims to determine that the login was via Azure AD, and instead of calling into ASP.NET Identity, I construct my own ClaimsIdentity, which has all my (application specific) claim information which my application recognises as an admin user.
Now, admin users navigate to https://example.com/admin, click the login button, are redirected to the Azure AD login, and windup back at the application as an admin user.
Your best bet would be to leverage Azue AD Access Control Services (ACS) and setup the Identity Providers to include Azure AD, Facebook, et al. See the documentation here: http://azure.microsoft.com/en-us/documentation/articles/active-directory-dotnet-how-to-use-access-control/
ACS can indeed be used, however as you have already implemented Google/Facebook signin I recommend that you directly integrate with Azure AD instead of going through an intermediate STS like ACS/thinktecture.
If your app' signin experience involves the user clicking on "Signin with Google/Signin with Facebook" stickers - you can add "Signin with your company' account." (there's even a recommended branding style: http://msdn.microsoft.com/en-us/library/azure/dn132598.aspx
If you app performs realm discovery and forwards the user to the appropriate IdP (employing a text box saying something like "Enter your email address to sign in") - then you could add matching for your company name email addresses and forward those users to AAD.
In both cases, your application will issue an SSO request to SSO endpoint of AAD: https://login.windows.net/{company domain name/id}/{wsfed/saml/oauth2}. If you're using .Net, WSFed, this should see you through: http://msdn.microsoft.com/en-us/library/azure/dn151789.aspx. Look for the code:
SignInRequestMessage sirm = FederatedAuthentication.WSFederationAuthenticationModule.CreateSignInRequest("", HttpContext.Request.RawUrl, false);
result = Redirect(sirm.RequestUrl.ToString());
There's also an OpenIdConnect sample here: http://msdn.microsoft.com/en-us/library/azure/dn151789.aspx