Session timeout with azure Ad .net core 2.0 - c#

I am trying to authenticate .net core 2.0 application with the Azure ad. I got it successful with authentication. But I need to session timeout after idle time.
Please find my startup.cs config
Configure
logger.AddConsole(Configuration.GetSection("Logging"));
logger.AddDebug((category, logLevel) => (logLevel >= LogLevel.Trace));
app.UseResponseCaching();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseSession();
app.UseAuthentication();
ConfigureServices
services.AddAuthentication(options =>
{
options.DefaultScheme= CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.ClientId = Configuration["Authentication:AzureAd:ClientId"];
options.Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"];
options.ClientSecret = Configuration["Authentication:ClientSecret"];
options.CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.IdToken;
})
.AddCookie();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(1);
options.CookieHttpOnly = true;
});

As the section Implementation Details under Working with Session State states as follows:
The server uses the IdleTimeout property to determine how long a session can be idle before its contents are abandoned. This property is independent of the cookie expiration. Each request that passes through the Session middleware (read from or written to) resets the timeout.
I enabled the session state, then set session values in an action and read them in another action. Per my test, your configuration for AddSession would issue a cookie with the default name .AspNetCore.Session and contains the session ID to the browser. The IdleTimeout is 1 minute and if you read or update the session values, then the IdleTimeout would be reset.
UPDATE:
AFAIK, there is no SessionEvents under SessionOptions when using services.AddSession. Per my understanding, you could set the Cookie expire time when using cookie auth, then add the processing to remove the session values and send the sign-out request to AAD when the cookie is invalid. Here is my configuration, you could refer to it as follows:
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container.
services.AddMvc();
// Add Authentication services.
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
// Configure the OWIN pipeline to use OpenID Connect auth.
.AddOpenIdConnect(option =>
{
option.ClientId = Configuration["AzureAD:ClientId"];
option.Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]);
option.SignedOutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"];
option.Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
};
})// Configure the OWIN pipeline to use cookie auth.
.AddCookie(op => {
op.ExpireTimeSpan = TimeSpan.FromMinutes(20);
op.LoginPath = "/Account/Login";
op.Events.OnRedirectToLogin =async(context) =>
{
//Clean the session values
context.HttpContext.Session.Clear();
//Sign-out to AAD
await context.HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
//Redirect to op.LoginPath ("/Account/Login") for logging again
context.Response.Redirect(context.RedirectUri);
};
});
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.CookieHttpOnly = true;
});
}

Related

Users being redirected to login even when user is authenticated ASP.NET Core

Im trying to set up authentication using cookies on my ASP.NET core project and for some reason whenever I attempt to access any route with the [Authorize] attribute, it attempts to redirect me to /Accounts/Login, even when the user is authenticated.
I have checked to make sure that the cookies are set on the client side and they are.
Here is my code:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var services = builder.Services;
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Name = "auth_cookie";
options.Cookie.IsEssential = true;
options.LoginPath = "/auth/login";
options.LogoutPath = "/auth/logout";
#if DEBUG
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
#else
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
#endif
options.Cookie.SameSite = SameSiteMode.Lax;
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
{
Console.WriteLine(context.RedirectUri);
context.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnSignedIn = context =>
{
Console.WriteLine("User signed in");
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:4200")
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddSession();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("default");
app.UseCookiePolicy();
app.UseSession();
app.UseMvc();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapDefaultControllerRoute();
app.Run();
The things that I have tried to do is change the order of UseAuthroization and UseAuthentication and change the order of other things.
I also suspect that It might be caused because of the way I authenticate users. Heres how the flow works.
User goes to the auth/login page
User gets redirected to googles login form
Google redirects users back to API. Which creates a new claims identity which then logs
the user in. This claim sends cookie back.
User gets redirected to frontend page
Here is the code with my login function that runs when google redirects back to it
//Above is a function to get a token from google which I know works correctly
var claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userInfo.Id),
//...
}, "Cookies");
await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
});
#if DEBUG
return LocalStorage.AddOrganization(org) ? Redirect($"http://localhost:4200/org/{HttpUtility.UrlEncode(org.Domain)}/settings/setup") : Redirect($"http://localhost:4200/org/{HttpUtility.UrlEncode(org.Domain)}/dashboard");
#else
return LocalStorage.AddOrganization(org) ? Redirect($"http://localhost:4200/org/{HttpUtility.UrlEncode(org.Domain)}/settings/setup") : Problem($"http://localhost:4200/org/{HttpUtility.UrlEncode(org.Domain)}/dashboard");
#endif

OIDC login fails with 'Correlation failed' - 'cookie not found' while cookie is present

I'm using IdentityServer 4 to provide authentication and autorisation for my web app, using an external login provider (Microsoft).
This works fine when I run both IdentityServer and my web app locally.
However, when I publish the Identityserver project to Azure, it no longer works.
When I connect my locally running web app to the published IdentityServer, after returning from the Microsoft login page, the web app fails with the error 'Correlation failed. unknown location'.
The output from the web app shows:
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:
Warning: '.AspNetCore.Correlation.oidc.xxxxxxxxxxxxxxxxxxx' cookie not found.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:
Information: Error from RemoteAuthentication: Correlation failed..
However, when I check my browser, a cookie with the exact name '.AspNetCore.Correlation.oidc.xxxxxxxxxxxxxxxxxxx' does exist..
Here's the startup.cs from the web app:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services
.AddTransient<ApiService>();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie()
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = Configuration.GetSection("IdentityServer").GetValue<string>("AuthorityUrl");
//options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("nl-NL"),
new CultureInfo("en-US")
};
options.DefaultRequestCulture = new RequestCulture("nl-NL", "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller}/{action}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
For me, Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler was logging this error:
'".AspNetCore.Correlation.OpenIdConnect.84ee_7zFbvb_w264b0SPRmS1OTKCeDhmzQ6awHoJ5gA"' cookie not found.
After looking at Chrome developer tools, I could see that the Correlation cookie was being stripped by the browser because the cookie's SameSite attribute was set to "None" yet the "Secure" attribute was not set. Chrome didn't like this.
I added the following statement in my Startup.Configure method.
*NOTE: This has to be added before app.UseAuthentication() and app.UseAuthorization().
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.Always
});
I have resolved this issue by adding below code
options.Events = new OpenIdConnectEvents
{
OnMessageReceived = OnMessageReceived,
OnRemoteFailure = context => {
context.Response.Redirect("/");
context.HandleResponse();
return Task.FromResult(0);
}
};
The problem was that the IdentityServer was still using AddDeveloperSigningCredential
which worked fine locally, but not in Azure.
By adding a certicate and this code it worked perfectly:
X509Certificate2 cert = null;
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
false);
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
cert = certCollection[0];
Log.Logger.Information($"Successfully loaded cert from registry: {cert.Thumbprint}");
}
}
// Fallback to local file for development
if (cert == null)
{
cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "example.pfx"), "exportpassword");
Log.Logger.Information($"Falling back to cert from file. Successfully loaded: {cert.Thumbprint}");
}
Finally I fixed it by
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.None
});
Before applying the change, I noticed the cookie's same site was marked as Strict.
I changed the code to SameSiteMode.Lax, and it didn't work.
So I changed again to the SameSiteMode.None, it works!
Make sure the user didn't bookmark your login page instead of your home page. This was the problem I had with Cognito.
In order to get it working, I had to combine Jeff Tian's solution with Scope Creep's solution:
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.None,
Secure = CookieSecurePolicy.Always
});
Then I also had to add an extra line to ConfigureServices:
options.Cookie.SameSite = SameSiteMode.None;
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.None; // I ADDED THIS LINE!!!
options.LoginPath = "/home/NotAuthorized";
...
Turning on Sessions in Asp.net also fixes the issue, and MayankGaur's solution also works, but I really didn't want to suppress errors that might make things easier to debug in the future.

How to use 2 cookies for auth?

I have 2 devices, pc and a special tablet. I want to have with same app .net core 2.0 2 cookies, or cookies scheme with 2 auth cookies, because for pc I want to expire in 5 minutes and for that special tablet to not expire at all. How to do that. Now I have this...
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "myScheme",
ExpireTimeSpan = TimeSpan.FromSeconds(300),
CookiePath = "/",
CookieSecure = env.IsDevelopment() ? CookieSecurePolicy.SameAsRequest : CookieSecurePolicy.Always
});
I think I could use UseWhen method or I don't know...
Or to sign in on different cookies?
Check Using cookie authentication without ASP.NET Core Identity
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/auth";
//https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookiebuilder?view=aspnetcore-2.1
options.Cookie = new CookieBuilder
{
Name = "CustomCookie",
HttpOnly = false
};
});
}
public async void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
}

Getting Correlation failed exception during OIDC auth

Right now I am developing asp net core 2.0 web site and I am adding authorization.
I have existing auth server build using identity server 4. I added new client with Implicit grant type. When I run locally I am successfully redirected to identity server and than after login back to we site. But when I deployed web site I am getting
An unhandled exception occurred while processing the request.
Exception: Correlation failed.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler+<HandleRequestAsync>d__12.MoveNext()
when redirected back after login. What can case the issue ?
Btw, here is my startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "mvcimplicit";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://identity.************.com/";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc.client";
options.SaveTokens = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
Do you have any ideas ?
May be you have some sort of cluster without exlicity defined data protection? Its potentially can produce behaviour like you describe.

Why is ASP.NET Core Identity 2.0 Authorize filter causing me to get a 404?

I have a controller that I want to restrict only to a specific role, let's say admin. After setting a user with the admin role, I can validate that he's on that role using the IsInRoleAsync method (which returns true). When setting the attribute with [Authorize(Roles = "admin")] I get a 404 with that very same user . I'm using bearer tokens (I don't think that is relevant but anyway) and here's what I've done to try debugging:
Controller w/o [Authorize] : the resource is returned. [OK]
Controller with [Authorize] : the resource is returned only when I use the Authentication: Bearer [access token] [OK]
Controller with [Authorize(Roles = "admin")] : even after logging in with the user that has the role set, I get the 404 [NOK]
I don't know if I'm missing some configuration, but here's my ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict(opt =>
{
opt.AddEntityFrameworkCoreStores<ApplicationDbContext>();
opt.AddMvcBinders();
opt.EnableTokenEndpoint("/api/token");
opt.AllowPasswordFlow();
opt.DisableHttpsRequirement(); //for dev only!
opt.UseJsonWebTokens();
opt.AddEphemeralSigningKey();
opt.AllowRefreshTokenFlow();
opt.SetAccessTokenLifetime(TimeSpan.FromMinutes(5));
});
services.AddAuthentication(options =>
{
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OAuthValidationConstants.Schemes.Bearer;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "http://localhost:44337/";
options.Audience = "resource_server";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = OpenIdConnectConstants.Claims.Subject,
RoleClaimType = OpenIdConnectConstants.Claims.Role
};
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
// Add application services.
options.ClaimsIdentity.UserNameClaimType= OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddSingleton(typeof(RoleManager<ApplicationUser>));
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
You likely get a 404 response because Identity - which is automatically configured as the default authentication, sign-in/sign-out and challenge/forbidden scheme by services.AddIdentity() - tries to redirect you to the "access denied page" (Account/AccessDenied by default), that probably doesn't exist in your application.
Try to override the default challenge/forbidden scheme to see if it fixes your issue:
services.AddAuthentication(options =>
{
// ...
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
});
To fix your second issue, make sure the JWT claims mapping feature is disabled. If it's not, the JWT handler will "convert" all your role claims to ClaimTypes.Role, which won't work as you configured it to use role as the role claim used by ClaimsPrincipal.IsInRole(...) (RoleClaimType = OpenIdConnectConstants.Claims.Role).
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// ...
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new JwtSecurityTokenHandler
{
// Disable the built-in JWT claims mapping feature.
InboundClaimTypeMap = new Dictionary<string, string>()
});
});
I think that what you need is to check claims, not roles. Add an AuthorizeAttribute such as:
[Authorize(Policy = "AdminOnly")]
And then configure a policy that requires a claim:
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireClaim(OpenIdConnectConstants.Claims.Role, "Admin"));
});
Or, for debugging purposes or more advanced validation, you could have:
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireAssertion(ctx =>
{
//do your checks
return true;
}));
});

Categories

Resources