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

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

Related

Authenticating a Blazor WebAssembly app with Azure Active Directory

I've already got an existing Blazor WebAssembly app and I'm attempting to add authentication with Azure Active Directory.
I've added the Microsoft.Authentication.WebAssembly.Msal nuget.
In my server's Program.cs, I've added the following code:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.LoginMode = "redirect";
});
And I've added the following to my appsettings.json
"AzureAd": {
"Instance": "xxxxxxxxxxx",
"Domain": "xxxxxxxxxxx",
"TenantId": "xxxxxxxxxxx",
"ClientId": "xxxxxxxxxxx",
"CallbackPath": "xxxxxxxxxxx"
},
I'm struggling to understand what else I need to add so that when I run the app, I get the Microsoft sign in screen.
You need to require authorization for parts of the application, or for the entire app. By default, all routes inside the application are open to the public. If you want to shield any of these routes, you need to explicitly ask for authorization.
How to do this is documented here: Secure ASP.NET Core Blazor WebAssembly.
In Blazor WebAssembly apps, authorization checks can be bypassed because all client-side code can be modified by users. The same is true for all client-side app technologies, including JavaScript SPA frameworks or native apps for any operating system.
Always perform authorization checks on the server within any API endpoints accessed by your client-side app.
If you host the app on a hosting option like a Static Web App, you can have the Azure platform enforce authorization without having to implement anything manually.
Authenticate and authorize Static Web Apps.
You can create a new blazor web assemebly application and choose Microsoft identity platform and then modify the appsettings.json file.
You need to add 3 parts, authentication injection in Program.cs, configurations in appsettings.json, and the module for sign view, 2 of them you already had, and the left is the sign in/out button in the top navigation bar, so that you can click it to redirect to Microsoft sign in page. You can create the template project and copy the razor components into your project.
LoginDisplay.razor:
#using Microsoft.AspNetCore.Components.Authorization
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#inject NavigationManager Navigation
#inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity?.Name!
<button class="nav-link btn btn-link" #onclick="BeginLogout">Log out</button>
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
#code{
private async Task BeginLogout(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

Asp.Netcore Web app with Azure AD b2c redirect url

Azure AD B2C is setup for authentication in Asp.netCore web app. The authentication process works perfectly. After authentication, the user needs to be redirected back the page where the login was initiated at.
the way the current flow happens:
This is the button <a class="btn btn-primary" asp-area="AzureADB2C" asp-controller="Account" asp-action="SignIn">Sign in</a>
PublicPage (has an sign-in/register button) on part of page that user need be authenticated to interact with -> signin button clicked -> redirected to Azure AD B2C -> user returned to IndexPage.
the way the needs to be:
PublicPage (has an sign-in/register button) on part of page that user need be authenticated to interact with -> signin button clicked -> redirected to Azure AD B2C -> user returned to PublicPage.
EDIT
#Jit_MSFT thanks for the suggestion, but I'm not exactly sure where to add those configurations.
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => {
Configuration.Bind("AzureAdB2C", options);
});
The settings above don't have those options. Also there are several page need to have that dynamic ability.
context.Properties.RedirectUri = "/xxxx;
this seems like i wold be locked into one page on the returnUrl
In your AccountController, please define the SignIn method as something like:
public async Task SignIn()
{
var redirectUri = ... // your redirect URI
await HttpContext.ChallengeAsync(AzureADB2CDefaults.AuthenticationScheme,
new AuthenticationProperties { RedirectUri = redirectUri });
}
You may also check other details and options in this answer
In addition, please check if, in Azure AD, you have to register your client app with a matching redirect URI (more details here) :)

.NET CORE and ADFS no Claims Available

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].

Custom User Flow not logging the user in, returns to AzureADB2C/Account/Error

I have created a User Flow in Azure AD B2C which is being used to register new users. When a new user registers they are being redirected to the AzureADB2C/Account/Error page of my site and they are not logged in. If I then use the sing_in_up policy link the user is then signed in without having to authenticate. I believe I am having an issue with reading the returned credentials after creating the account but I am not too sure.
Here is my link that is directing the user to the signup page:
<a class="btn btn-light mx-1" asp-controller="Account" asp-action="SignUp">
<strong>Sing Up Free</strong>
</a>
This is the controller method:
public IActionResult SignUp()
{
return this.Challenge(
new AuthenticationProperties { RedirectUri = "/" }, "B2C_1_sign_up");
}
This is my OpenIdConnectionOptions:
private Action<OpenIdConnectOptions> GetOpenIdSignUpOptions(string policy)
=> options =>
{
options.MetadataAddress =
"https://login.microsoftonline.com/[Domain].onmicrosoft.com/v2.0/.well-known/openid-configuration?p=" + policy;
options.ClientId = "[***]";
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SignedOutCallbackPath = "/signout/" + policy;
options.CallbackPath = "/signin-oidc";
options.SignedOutRedirectUri = "/";
};
Where [Domain] = the Azure AD B2C domain and [***] = the Azure AD B2C ClientId.
This is my service configuration:
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options =>
Configuration.Bind("AzureAdB2C", options)
);
services.AddAuthentication(
options => options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme)
.AddOpenIdConnect("B2C_1_sign_up", GetOpenIdSignUpOptions("B2C_1_sign_up"))
.AddCookie();
The B2C_1_sign_up_in and custom B2C_1_sign_up policies are Identical with the exception of the B2C_1_sign_up_in containing the Unified sign up or sign in page. Any help troubleshooting this is greatly appreciated.
Here are few points to check:
Please ensure that you have tested the custom sign up policy from Azure AD B2C blade from the portal, Check if it working fine or not.
Make sure to configure CORS for the customized page and the location where you have stored.
Validate that you're ready by doing the following:
Go to the www.test-cors.org website, and then paste the URL in the
Remote URL box.
Click Send Request.
If you receive an error, make sure that your CORS settings are correct. You might also need to clear your browser cache or open an in-private browsing session by pressing Ctrl+Shift+P.
Also please make sure to change the base policy with right content definition.
Please refer this link for reference.

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.