C# - Azure SSO token expiring throwing error - c#

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.

Related

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

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

Website Azure connection on mulitple subdomains

We are hosting a website on our webserver. That website needs to connect to Azure/Adfs. Users need to login through Azure/Adfs to access some parts of the site.
But it only works half. I can connect on "customer.nl", but on "subdomain.customer.nl" I get a "NONCE error".
There is a "Startup" class, which inherits from "UmbracoDefaultOwinStartup" (an Umbraco override for the regular OwinStartup). The class has a "ConfigureAuth" method, which sets the configurationparameters. One of them is the RedirectUri, and it's set (via web.config) to "customer.nl".
The "startup" code:
[assembly: OwinStartup(typeof(Ip.Startup))]
namespace Customername {
public class Startup : UmbracoDefaultOwinStartup {
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
public new void Configuration(IAppBuilder app) {
ConfigureAuth(app);
app.MapSignalR();
base.Configuration(app);
}
public void ConfigureAuth(IAppBuilder app) {
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions(){
CookieManager = new SystemWebCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters() {
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = OnAuthenticationFailed
}
});
}
}
}
If I try to login on "subdomain.customer.nl", I redirected to login.microsoftonline.com but I see a "redirect_url=customer.nl" in the URL.
The function to redirect a unauthenticated user is:
public void SignIn(string ReturnUrl = "/") {
if (!Request.IsAuthenticated) {
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = ReturnUrl },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
But changing the RedirectUri in this function doesn't change the 'Redirect_Uri' in the login.microsoftonline.com url.
If I login on subdomain.customer.nl, I get returned to customer.nl with the following querystring (I've decoded the URL):
https://www.customer.nl/?errormessage=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.
My guess is that the NONCE error pops up when the redirect_uri doesn't match the origin-url (subdomain.customer.nl != customer.nl).
Is this correct? And if so, how can I change the Redirect_Uri to the subdomain a user is visiting? Setting it on startup isn't the way to go, it seems.
• Firstly, I would suggest you to please ensure that public DNS records exist for the subdomains that you want to connect to through the base domain URL, i.e., ‘customer.nl’. The public DNS records for the subdomains can be ‘A’ host records, ‘TXT’ records but need to be configured correctly within your public DNS server and pointing to a public IP address if independent web applications are hosted on them.
• Secondly, since you seem to use Azure AD authentication in your website for redirecting to the subdomain, I would suggest you configure the redirect URI for the concerned subdomains in the Azure AD registered application for the base domain such that after successful Azure AD authentication, the web application gets correctly redirected to subdomain page as desired.
For more information on the above, kindly refer to the documentation link below: -
https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad
But changing the RedirectUri in this function doesn't change the 'Redirect_Uri' in the login.microsoftonline.com url
You can do the above by delegating the required API permissions and scope to the Azure function application in your registered Azure AD application. Kindly refer to the documentation link below for your reference: -
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent
Also, the domains for the authentication request and the response need to be matched as it stores the ‘nonce’ and the ‘state’ for CSRF login attacks mitigation. Thus, I would like to suggest you consider the scenario below for different clients (as per your redirection mechanism) and take advantage of SSO: -
a) The user logs in into the first application (customer.nl). The callback URL belongs to this app.
b) After processing the callback (on the ‘parseHash’ callback function), redirect the user to the subdomain URL.
c) When the user lands on the subdomain URL app, the app will see that there’s no session for the user and ask Azure AD for authentication (either authorize () or checkSession()). If the user already has a session in Azure AD, there will be no prompt to the user and a new authentication response will be provided to the app.
If you are using universal login (as opposed to embedded login as above), when the user clicks on “Login” on the base domain URL (customer.nl) app, you send the user directly to the SPA, pointing to a login initiation endpoint (e.g.: - https://app.mydomain.com/login 1), and have the subdomain URL app start the actual login flow.
For more information regarding the above, I request you to please refer the below link: -
https://community.auth0.com/t/log-in-from-different-subdomain-produces-state-error/19116

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.

Office365 Single-Sign out

I have implemented Single-Sign on in an existing C# Asp.Net MVC 4.5 application and therefore used Owin middleware and OpenIdConnectAuthentication.
The authentication and authorization works fine, but now I have following problem:
I sign in to my application by using AzureAD as identity provider
I sign in to Office365 in another browser tab
I sign out of my application - get redirected to the identity provider and also automatically sign out there
Office365 automatically signs out within the other tab
I do not have configured Single Sign out (so I didn't specified a Logout Url within the App Registration and neither in the configuration code), but I am still signed out of Office365. This is annoying for the customer, as he always uses Outlook365 within the browser.
How can I prevent Office365 from signing out the user automatically.
Here is a simplified code of my configuration of OpenIdConnect:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
// ... my sign in logic ...
context.OwinContext.Response.Redirect(homeUrl);
return Task.FromResult(0);
},
},
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType
});
And here is how I sign out of the application:
context.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = redirectUrl }, OpenIdConnectAuthenticationDefaults.AuthenticationType);

How to use Azure AD for authentication for multiple instances of same application

I am new to Azure Active Directory authentication and I am trying to figuring out how we can implement our requirements for Azure Active Directory Authentication.
We have multiple instances of the same web application. i.e. There is one virtual directory and one database for each client. Users from one instance should not be allowed to access other instances of the application. Can we achieve this using a Single Active Directory with all users in it and a single Azure AD application or do we need to create Active Directory and a separate Azure AD application for each client? The restriction will be implemented through application code, but is there any way to group users so we can get some value in authentication response to identify which instance the user can access?
1. We have multiple instances of the same web application. i.e. There is one virtual directory and one database for each client. Users from
one instance should not be allowed to access other instances of the
application. Can we achieve this using a Single Active Directory with
all users in it?
Yes, you can do that.
You can create Azure AD group for each database in Azure SQL Server and assign SQL role to the group. Then if you want to enable one user to access one database, you just need to add the user to the group. For more details, please refer to the document
The steps are as below
Create an Azure AD administrator for Azure SQL server
Connect-AzAccount
Set-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName "Group-23" -ServerName "demo_server" -DisplayName "user name" -ObjectId "user object id"
Create Azure AD group for each database
a. Use above Azure AD use login SQL server vai SSMS
b. Create
CREATE USER [GroupNmae] FROM EXTERNAL PROVIDER
ALTER ROLE db_owner ADD MEMBER [GroupNmae]
Add users to AD group
Connect-AzureAD
Add-AzureADGroupMember -ObjectId <group id> -RefObjectId <user id>
2. The restriction will be implemented through application code, but is there any way to group users so we can get some value in
authentication response to identify which instance the user can
access?
According to my test, we can use the AzureAD token role claim to implement. We can create app role for every database in Azure SQL server by assigning app role for the AD group. For more details about app role, please refer to the document
The detailed steps are as below.
Create Azure AD application
Configure app permisions
Add app role
Select the application you want to define app roles in. Then select Manifest.
Edit the app manifest by locating the appRoles setting and adding all
your Application Roles
For example
"appId": "8763f1c4-f988-489c-a51e-158e9ef97d6a",
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "your databse name",
"id": "<GUID>",
"isEnabled": true,
"description": "access database <your databse name>",
"value": "your databse name"
}
],
"availableToOtherTenants": false,
Code
a. Create Stratup.cs
public void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = appId,
ClientSecret = appSecret,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = "openid profile offline_access https://database.windows.net//.default",
ResponseType=OpenIdConnectResponseTypes.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
RoleClaimType = "roles"
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived= OnAuthorizationCodeReceived
}
}
);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
var idClient = var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithAuthority(authority)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
string[] scopes = "openid profile offline_access https://database.windows.net//.default".Split(' ');
AuthenticationResult result = await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync();
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
notification.Response.Redirect("/Error?message=" + notification.Exception.Message);
return Task.FromResult(0);
}
b. Get role value
ClaimsPrincipal.Current.FindFirst("roles").Value;
Regarding how to implement this, you can refer to the sample to get more details.

Categories

Resources