Can't connect ASP.NET MVC to Azure AD with OIDC - c#

I am trying to set up an SSO sign in to a ASP.NET MVC 5 application (.NET 4.8) with OpenID Connect. I'm using Azure Active Directory. The application is a brand new project made for testing purposes, and the only change I introduced to scaffolded code is in Startup.Auth.cs:
// automatically added usings:
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.OpenIdConnect;
// in public void ConfigureAuth(IAppBuilder app) method:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "{ClientId of AAD App}",
ClientSecret = "{Secret generated for the AAD app}",
CallbackPath = new PathString("/signin-microsoft"),
MetadataAddress = "https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration",
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://login.microsoftonline.com/{Directory (tenant) ID}/v2.0"
}
});
If I set ValidateIssuer to false, everything works fine - I manage to sign into the application with my organization email. But as soon as I set it to true, I start getting the following error:
IDX10205: Issuer validation failed. Issuer: '[PII is hidden]'. Did not match: validationParameters.ValidIssuer: '[PII is hidden]' or validationParameters.ValidIssuers: '[PII is hidden]'.
I tried changing the ValidIssuer to all options mentioned in this SO thread, but nothing works. The current ValidIssuer is the URL given in the MetadataAddress above, with concrete Directory (tenant) ID of the registered app.
As far as the registered AAD app goes, I've set both Access tokens (used for implicit flows) and ID tokens (used for implicit and hybrid flows) to true and Supported account types to Accounts in any organizational directory (Any Azure AD directory - Multitenant).
Any idea what I'm not getting here?

It was a bad Tennant ID after all.
I realized it by setting IdentityModelEventSource.ShowPII to true in Startup.Auth.cs, as seen in this answer: https://stackoverflow.com/a/55027625/2975357

Related

Get claims from jwt token into context.User.Claims

I'm trying to get claims from the JWT token into context.User.Claims in my ASP.NET Core 5 Web API. I'm using Azure and have registered an app in Azure AD in our tenant.
The code is in an Authorization handler.
When I read the JWT token (context.Request.Headers["Authorization"]) using JwtSecurityTokenHandler, I can get all claims, but my context.User.Claims is still empty.
The aud and iss values show up as:
[aud, https://graph.microsoft.com]
[iss, https://sts.windows.net/[tenant-id]/]
This is my Startup.ConfigureServices() method:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
option.Audience = audience;
option.Authority = authority;
option.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromHours(1),
ValidateLifetime = true,
ValidateIssuer = true,
ValidIssuer = authority,
ValidateAudience = true,
ValidAudience = audience
};
});
What am I supposed to put in audience and authority? I have tested the values I got from the JWT token (above).
I have also tried with:
authority (issuer): https://login.microsoftonline.com/[tenant-id]/v2.0
audience: app://[client-id]
and all combinations. Same result
Initially, try to decode the token using jwt.ms and check what claims the token contains.
For Audience parameter, you can use the Application ID URI (api://your_app_id) or scope (https://graph.microsoft.com).
For Authority parameter, you can use the address of the token-issuing authentication server. Please note that, issuer value differs depending on the type of token you are generating (v1.0/v2.0).
For v1.0 token -> https://sts.windows.net< Azure AD Tenant GUID>/
For v2.0 token -> https://login.microsoftonline.com<Azure AD Tenant GUID>/v2.0
To confirm and know more about the parameters, refer to the blog by Jeffrey Fritz.
To receive the claims, make sure to add [Authorize] attribute with HTTP
context header.
Please check whether you included app.UseAuthorization or not.
Make sure to call the middleware in the order like below from this MS Doc:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
If the issue persists, then try to modify the Startup.cs -> ConfigureServices() method as mentioned in this blog.
You can refer to the links below that can give you some pointers to resolve the issue:
identityserver4 - User.Claims is empty ASP.NET 5.0 - Stack Overflow
.NET Core Web API HttpContext.User.Claimsare always null - Stack Overflow
I suspect you are getting the wrong type of JWT, with a nonce field in the JWT header, that does not validate properly in your own APIs. To fix this there is an Expose an API scope option.
There are some visual details about this from step 3 of my blog post from a few years back. You can then configure the issuer and audience as in my API code example config.

Trying to login with Azure AFDS on multiple domains

I'm trying to connect to an Azure AD server with an Umbraco website.
To start off, I have no knowledge of Azure. There is a third party who administers the Azure part.
We use OWIN to connect to Azure via OpenID.
OnStartup:
public void ConfigureAuth(IAppBuilder app){
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters(){
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications{
AuthenticationFailed = OnAuthenticationFailed
}
});
}
The SignIn function in the SurfaceController:
public void SignIn(string ReturnUrl = "/"){
if (!Request.IsAuthenticated) {
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = ReturnUrl },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Here come the non-working part.
If I test this site at a local domain (only available from within our office), it works.
If I test this site on a publicly-available staging domain, it works.
If I test this site on a live domain, it works.
But as soon as I change a sub-domain, I get send to the working domain with a "RequireNonce" error.
So for example:
https://customer.localdomain.com -> login -> I return logged in at https://customer.localdomain.com.
https://test.localdomain.com -> login -> I return to https://customer.localdomain.com (notice the domain), with a "Nonce-error".
https://customer.stagingdomain.com -> login -> I return logged in at https://customer.stagingdomain.com.
https://test.stagingdomain.com -> login -> I return to https://customer.stagingdomain.com (notice the domain), with a "Nonce-error".
https://www.livedomain.com -> login -> I return logged in at https://www.livedomain.com.
https://test.livedomain.com -> login -> I return to https://www.livedomain.com (notice the domain), with a "Nonce-error".
The complete error is:
IDX21323:
RequireNonce is '[PII is hidden]'.
OpenIdConnectProtocolValidationContext.Nonce was null,
OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null.
The nonce cannot be validated.
If you don't need to check the nonce, set OpenIdConnectProtocolValidator.
RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.
What can we do to resolve this problem? Our customer has a couple of subdomains (seperate sites) that all need this login functionality.
We've tried adding subdomains to a reply-list in Azure (well, the third party added them for us), but that didn't solve the problem.
Is it possible to just turn RequireNonce off somewhere?
Thank you JamesHamil-MSFT Posting your suggestion as an answer to help other community members .
"The problem was that the time or automatic reference program service binding a custom domain name.
After the application network management is configured. The Host IP that modifies the custom domain name points to a public IP that is gateway."
Please try checking that your domain is configured correctly and points to the correct gateway."
Please refer the below links for further information:
. Configure App Service with Application Gateway using PowerShell | MS DOC .
. SO THREAD for similar issue.

C# - Azure SSO token expiring throwing error

I am attempting to write a c# web app that uses Azure for a SSO provider.
I am using Owin as the middle layer.
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // Simplification (see note below)
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
});
}
So it logs in fine, but after 1 hour when i attempt to do AJAX requests (regardless of whether the page has refreshed) i am getting a CORS error because the token has expired.
How do i 'keep it alive' the token so that users don't have 1 hour to complete their work?
A token lifetime policy is a type of policy object that contains token lifetime rules. Use the properties of the policy to control specified token lifetimes. If no policy is set, the system enforces the default lifetime value.
You can set access token lifetime to one day so that you will not expired with one hour limit.
You can set the token lifetime configuration on your Service Principal, Application, or Tenant.
You'll need to use Powershell to create a policy describing the behavior you want, and link it to your service principal, tenant, or application. Keep in mind, if you're building a multi-tenant app, the owner of the tenant can overwrite your policy.
Note: Don't rely on the token lifetime in your app as it can change at any time.
You can set these properties using Azure AD Powershell Commands. Then run the following commands to set an access token lifetime:
1.Sign in to Powershell.
Connect-AzureAD -Confirm
2.Create a new policy to set the Access Token lifetime to 2 hours. You can change this to be between 10 minutes and 1 day.
New-AzureADPolicy -Definition #('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"24:00:00","MaxAgeSessionSingleFactor":"02:00:00"}}') -DisplayName "WebPolicyScenario" -IsOrganizationDefault $false -Type "TokenLifetimePolicy"
3.Get the policy's ObjectId.
Get-AzureAdPolicy
4.Link the new policy to your application. You can get the objectId of your app using the GraphExplorer.
Add-AzureADApplicationPolicy -Id <ObjectId of the Application> -RefObjectId <ObjectId of the Policy>
For more details, you could refer to this article about Azure AD Configurable Token Lifetime.

Working with Azure App Service Authentication on server side [duplicate]

We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.

Cookie expiry in ASP NET Core Authentication using Azure AD OpenIdConnect and custom middleware

I am currently struggling with setting the timeout on the cookie/auth token when authenticating my .NET Core App using Azure AD via the OpenIdConnect authentication model.
The sign-in scheme is being set in the ConfigureServices method via the following:
services.AddAuthentication(options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
I am then setting up my configuration as follows:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "MyCookie",
ExpireTimeSpan = TimeSpan.FromHours(2)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions()
{
Authority = authorityUri.AbsoluteUri,
ClientId = azureOptions.ClientId,
ClientSecret = azureOptions.ClientSecret,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
await aAuthenticateMiddleware.OnAuthenticate(context, logger);
}
}
});
app.UseMiddleware<aAuthenticateMiddleware>();
Note, that I am not using the built in Identity (as its not practical for our purposes) but rather using a custom middleware.
Within the middleware layer I am checking whether the user is authenticated and if not a challenge is issued:
var authenticationProperties = new AuthenticationProperties() { RedirectUri = context.Request.Path.Value ?? "/" };
authenticationProperties.AllowRefresh = false;
authenticationProperties.IssuedUtc = DateTime.Now;
authenticationProperties.ExpiresUtc = DateTime.Now.AddHours(2);
await context.Authentication.ChallengeAsync(
authenticationManager.IdentityProvider.AuthenticationScheme,
authenticationProperties,
ChallengeBehavior.Automatic
);
This is all works fine and authenticates the user correctly etc however this is issuing the auth token (and cookie) with a 15 minute expiry and ignoring my 2 hour expiry that I have tried setting.
I have been referring to the latest source examples from GitHub from the aspnet/security repository for examples.... however none of these mention anything about overriding the default expiry issued.
https://github.com/aspnet/Security/tree/dev/samples/OpenIdConnect.AzureAdSample
Most examples I have found are still referencing the old AspNet libraries rather than the AspNetCore libraries.
Some articles suggest that using the SignInAsync with persistent set to True allows the ExpireTimeSpan to be honored, however this throws a "Not Supported Exception" when calling it. Perhaps SignInAsync is not supported via Azure AD?
Does anyone have any insight on how to achieve this?
in UseOpenIdConnectAuthentication set UseTokenLifetime = false

Categories

Resources