MVC application Azure AD and custom authentication support - azure

We have implemented Custom token based authentication in the MVC application. Now we enabled Azure AD as well using the OpenID Connect as described below.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ADClientId,
Authority = ADauthority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
if (context.Request.Path.Value == "/Account/ExternalLogin" || (context.Request.Path.Value == "/Account/LogOff"))
{
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
}
else
{
context.State = Microsoft.Owin.Security.Notifications.NotificationResultState.Skipped;
context.HandleResponse();
}
return Task.FromResult(0);
},
}
We need to modify the scenario like below. If you have any technical suggestion let me know
1) Login page - get user email address
2) Check for user id and if that is Azure AD email - then take to the Microsoft authentication page where the user enters the password
3) If the user enters custom user id , handle the password page in the application's internal authentication flow

If your requirement is pivoted upon examining if a provided email address is from an existing user account in your Azure Active Directory tenant, then you can utilize the Microsoft Graph to query and confirm.
For example, the following Graph Api REST call would help determine if a provided email address is that of an existing user in your tenant.
https://graph.microsoft.com/v1.0/users?$filter=startswith(mail%2C+'email#domain.com')

Related

Azure App Service Authentication / Authorization and Custom JWT Token

In my web project i want to enable the user to login with username / password and Microsoft Account.
Tech - Stack:
Asp.Net Core WebApi
Angular
Azure App Service
First i created the username / password login. Like this:
StartUp.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Configuration["JWTKey"].ToString())),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
});
Login Method:
public async Task<IActionResult> ClassicAuth(AuthRequest authRequest)
{
tbl_Person person = await _standardRepository.Login(authRequest.Username, authRequest.Password);
if (person != null)
{
var claims = new[]
{
new Claim(ClaimTypes.GivenName, person.PER_T_Firstname),
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_config["JWTKey"].ToString()));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddHours(24),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return Ok(tokenHandler.WriteToken(token));
}
else
return Unauthorized("Invalid login data");
}
And secured my api enpoints with [Authorize].So far so good...that works.
Now i want to add a login method with Microsoft Account. I use Azure App Service Authentication / Authorization for that (https://learn.microsoft.com/de-de/azure/app-service/overview-authentication-authorization).
I configured the auth provider and i'm able to start the auth flow with a custom link in my angular app:
Login with Microsoft - Account
This works and i can retrieve the access token from my angular app with this:
this.httpClient.get("https://mysite.azurewebsites.net/.auth/me").subscribe(res => {
console.log(res[0].access_token);
});
Now the problem:
access_token seems not a valid JWT Token. If i copy the token and go to https://jwt.io/ it is invalid.
When i pass the token to my API i get a 401 - Response. With seems logical because my API checks if the JWT Token is signed with my custom JWT Key and not the Key from Microsoft.
How can I make both login methods work together? I may have some basic understanding problems at the moment.
It seems you want your Angular app calling an ASP.NET Core Web API secured with Azure Active Directory, here is a sample works well for that.
The most important step is register the app in AAD.
By the way, if you want to enable users to login one project with multiple ways in azure, you can use multiple sign-in providers.

How to retrieve the SignIn user's group/role "MailNickName" from AAD using AAD authentication without OpenID

I have created Azure Web App with AAD authentication.
But unable to read the Signed in User group details.
I have created web app/app registration Like this https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad.
Once the Web App authenticated application redirect https://contoso.azurewebsites.net/.auth/login/done
Once AAD user authorized. we need to pull the Group details (I Need MailNickName).
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"]}");
var tenantId = ClaimsPrincipal.Current.Claims.Single(x => x.Type == "http://schemas.microsoft.com/identity/claims/tenantid").Value;
var userId =ClaimsPrincipal.Current.Claims.Single(x => x.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var httpResponse =httpClient.GetAsync("https://graph.microsoft.com/v1.0/me/memberOf").Result;
httpResponse.EnsureSuccessStatusCode();
var jsonResult =await httpResponse.Content.ReadAsStringAsync();
/* IUserMemberOfCollectionWithReferencesPage groups;
while (groups.Count > 0)
{
foreach (Group g in groups)
{
rol.Add(g.MailNickname);
}
if (groups.NextPageRequest != null)
{
groups = await groups.NextPageRequest.GetAsync();
}
else
{
break;
}
}
return rol.Where(x => x.Length <= 8).ToList();*/
}
Note : We are not using OpenID Connect.
I need the lsit of (g.MailNickname) for the Signed User. I got unauthorized error.
Thanks in Advance.
You need to grant the application Directory.Read.All delegated permission and grant admin consent.
Find the application you registered on Azure portal->click API permissions->choose Microsoft Graph->Delegated permissions->check Directory.Read.All
Remember to click 'Grant admin consent' button.
Then change the additionaloginparams, the resource should be https://graph.microsoft.com. Refer to this article for more details(You can focus on configuring Web App to Ask for Access to Correct Resource part).

Set display name / username for email local accounts in Azure AD B2C?

I'm new to Azure AD B2C and have set up a site that correctly authenticates using local accounts (email only). When the validation request comes back, I see the email address under the 'emails' claim, but the 'name' claim comes back as 'unknown'.
Looking in Azure portal, the account is created but the name is unset and is 'unknown' for all users that register. This isn't what I was expecting. I would prefer that the 'name' be set to the email address by default so that it is easier to find the account in the portal, since we aren't collecting a 'Display Name' at all for this account type (user already enters given and surname).
Do I have something configured incorrectly, and is there a way to default the username to the email address for local, email only accounts?
Azure AD B2C does not "auto-populate" any fields.
When you setup your sign-up policy or unified sign-up/sign-in policy you get to pick the Sign-up attributes. These are the attributes that are show to the user for him/her to provide and are then stored in Azure AD B2C.
Anything that the user is not prompted for is left empty or in a few select cases (like name as you have observed) set to 'unknown'.
Azure AD B2C can not make assumptions as to what to pre-populate a given attribute with. While you might find it acceptable to use the email as the default for the name, others might not. Another example, the display name, for some, can be prepopulated with "{Given name} {Surname}", but for others, it's the other way around "{Surname, Givenname}".
What you are effectively asking for is an easy way to configure defaults for some attributes which is not that's available today. You can request this feature in the Azure AD B2C UserVoice forum.
At this time, you have two options:
Force your users to explicitly provide this value by select it as a sign-up attribute in your policy.
Add some code that updates these attributes with whatever logic you want (for example in the controller that processes new sign-ups or via a headless client running periodically).
Here's a quick & dirty snippet of .Net code that you can use for this (assuming you want to do this in the auth pipeline (Startup.Auth.cs):
private async Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
try
{
var userObjectId = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
// You'll need to register a separate app for this.
// This app will need APPLICATION (not Delegated) Directory.Read permissions
// Check out this link for more info:
// https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(string.Format(graphAuthority, tenant));
var t = await authContext.AcquireTokenAsync(graphResource, new ClientCredential(graphClientId, graphClientSecret));
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.AccessToken);
var url = graphResource + tenant + "/users/" + userObjectId + "/?api-version=1.6";
var name = "myDisplayName";
var content = new StringContent("{ \"displayName\":\"" + name + "\" }", Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
You'll reference this method when you setup your OpenIdConnectAuthenticationOptions like so:
new OpenIdConnectAuthenticationOptions
{
// (...)
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated,
},
// (...)
};
I wrote this extension:
public static class ClaimsPrincipal
{
public static string Username(this System.Security.Claims.ClaimsPrincipal user)=> user.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
}
Now you can use
User.Identity.Name for name if you have this in your OpenId config in the Startup.cs
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
and User.Username if you include the extension

Azure Multi-Tenant Application with Windows Live ID Authentication

We are successfully authenticating the Azure AD users from different subscription using Azure AD Multi-tenant application but unable to authenticate the Windows Live ID accounts.
To authenticate the live ID accounts we use the Windows Live ID identity provider with Azure Access Control Service (ACS), its working fine with Azure AD single tenant application but we are struggling to authenticate Azure AD users across subscriptions which can only be done by using the Azure AD multi-tenant application.
We follow this blog https://msdn.microsoft.com/en-us/library/azure/dn486924.aspx and it works for Single tenant application but when we try to configure the Azure AD app to multi-tenant and configure it with ACS getting the below error.
enter image description here
Is there any approach we authenticate the Windows Live ID and use the Azure Multi-Tenant Application?
You can authenticate Microsoft Account (live id) users in a multi tenant application by skipping ACS altogether and provisioning the Microsoft Account in directory tenants. One gotcha is that authenticating with a Microsoft Account requires you to fully specify the authentication endpoints by instantiating the tenant in the URL. You cannot use the /common endpoint because that relies on the user's home tenant, and an MSA user does not have one.
You add following code in your Account controller
public void SignIn(string directoryName = "common")
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Environment.Add("Authority", string.Format(ConfigurationManager.AppSettings["ida:Authority"] + "OAuth2/Authorize", directoryName));
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
and add this block in your startup.auth.cs
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
// we inject our own multitenant validation logic
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
object obj = null;
if (context.OwinContext.Environment.TryGetValue("Authority", out obj))
{
string authority = obj as string;
if (authority != null)
{
context.ProtocolMessage.IssuerAddress = authority;
}
}
if (context.OwinContext.Environment.TryGetValue("DomainHint", out obj))
{
string domainHint = obj as string;
if (domainHint != null)
{
context.ProtocolMessage.SetParameter("domain_hint", domainHint);
}
}
context.ProtocolMessage.RedirectUri = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path);
context.ProtocolMessage.PostLogoutRedirectUri = new UrlHelper(HttpContext.Current.Request.RequestContext).Action
("Index", "Home", null, HttpContext.Current.Request.Url.Scheme);
//context.ProtocolMessage.Resource = GraphAPIIdentifier;
context.ProtocolMessage.Resource = AzureResourceManagerIdentifier;
return Task.FromResult(0);
},
...
}
When you click on "SignIn" ask for "Azure AD name". Pass that variable to the Account/SignIn action. If the user will be present in the mentioned Azure AD, sign-in will be successful.

Using the MVC Authorize attribute with roles using Azure Active Directory + OWIN

I'm building a multi-tenant MVC5 app that follows very closely the sample guidance: https://github.com/AzureADSamples/WebApp-MultiTenant-OpenIdConnect-DotNet/
I'm authenticating against Azure Active Directory and have my own role names that I inject as a Role claim during the SecurityTokenvalidated event:
SecurityTokenValidated = (context) =>
{
// retriever caller data from the incoming principal
string upn = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
string tenantId = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var databaseConnectionString = RoleEnvironment.GetConfigurationSettingValue("DatabaseConnectionString");
AppAnalyzerUser appAnalyzerUser = null;
using (CloudContext dbContext = new CloudContext(databaseConnectionString))
{
if (dbContext.Office365Accounts.FirstOrDefault(x => x.AzureTokenId == tenantId) == null)
throw new GeneralErrorException("Account not found", "The domain that you used to authenticate has not registered.");
appAnalyzerUser = (from au in dbContext.AppAnalyzerUsers
.Include(x => x.Roles)
where au.UserPrincipalName == upn && au.AzureTokenId == tenantId
select au).FirstOrDefault();
if (appAnalyzerUser == null)
throw new AccountNotFoundException();
}
foreach (var role in appAnalyzerUser.Roles)
{
Claim roleClaim = new Claim(ClaimTypes.Role, role.RoleName);
context.AuthenticationTicket.Identity.AddClaim(roleClaim);
}
return Task.FromResult(0);
},
I've decorated some methods with the Authorize attribute like this:
[Authorize(Roles = "SystemAdministrator"), HttpGet]
public ActionResult Index()
{
return View();
}
and the authorize attribute correctly detects that a user is not in that role and sends them back to Azure to authenticate.
However what I see is that the user is already authenticated against Azure AD and is logged in to the app. They don't get the chance to choose a new user account on the Azure screen to log in. So when it bounces them back to Azure AD, Azure AD says "you're already logged in" and sends them right back to the app. The SecurityTokenValidated event fires repeatedly, over and over.
But the user still doesn't have the role required for the method, so they get bounced back to Azure for authentication, and obviously we get stuck in a loop.
Other than writing my own implementation of the Authorize attribute, is there some other approach to solve this problem?
Unfortunately you stumbled on a known issue of [Authorize]. For a description and possible solutions see https://github.com/aspnet/Mvc/issues/634 - at this point writing a custom attribute is probably the most streamlined workaround.

Resources