OpenIdConnect Authentication Cookie not being deleted on signing out - single-sign-on

I'm trying to implement OpenIdConnect as my authentication provider, using .NET6. I have the sign-in part working properly, however, on sign-out, the authentication cookie is not being deleted.
I've seen plenty of other threads referencing similar problems (this, for example provides a pretty good summary of several of these), but none of the approaches seem to work.
Broadly speaking, my authentication code is configured as follows:
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AuthenticateScheme;
options.DefaultSignOutScheme = AuthenticateScheme;
options.DefaultChallengeScheme = ChallengeScheme;
})
.AddCookie(AuthenticateScheme, options =>
{
//trying to avoid having to add this!
//options.Events.OnSigningOut = async ctx =>
//{
// await Task.Run(() => ctx.HttpContext.Response.Cookies.Delete(CookieName));
//};
})
.AddOpenIdConnect(ChallengeScheme, options =>
{
options.SignInScheme = AuthenticateScheme;
options.SignOutScheme = AuthenticateScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.CallbackPath = CallbackPath;
options.UsePkce = true;
options.Authority = Authority;
options.ClientId = ClientId;
options.ClientSecret = ClientSecret;
options.Scope.Clear();
options.Scope.Add(OpenIdConnectScope.OpenId);
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.Scope.Add(OpenIdConnectScope.Email);
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles",
NameClaimType = "preferred_username",
ValidateIssuer = false
};
options.Events.OnRedirectToIdentityProvider = ctx =>
{
// Prevent redirect loop
if (ctx.Response.StatusCode == 401)
{
ctx.HandleResponse();
}
return Task.CompletedTask;
};
options.Events.OnAuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(context.Exception.Message));
return Task.CompletedTask;
};
});
And over on my Startup.cs, I have the following code within the Configure method:
app.Map("/Logout", map =>
{
map.Run(async appBuilder =>
{
// tried this, didn't work
//await appBuilder.SignOutAsync("MyAuthCookie", new AuthenticationProperties { RedirectUri = "/" });
// tried this, didn't work
//await appBuilder.SignOutAsync();
});
});
app.UseNotFoundHandler();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseGetaCategories();
app.UseGetaCategoriesFind();
app.UseAnonymousId();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "Default", pattern: "{controller}/{action}/{id?}");
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapContent();
});
Any suggestions would be greatly appreciated. For now, the only way I've been able to get this to work is to explicitly delete the cookie within the OnSigningOut event, but this doesn't feel like the right approach.

A more complete logout should look something like this:
/// <summary>
/// Do the logout
/// </summary>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout()
{
await HttpContext.SignOutAsync(AuthenticateScheme);
await HttpContext.SignOutAsync(ChallengeScheme);
//Important, this method should never return anything.
}
You might need to tweak the schema names if you don't use the default ones.
In the AddAuthentication method, I would se these settings, you want cookie for everything except the challenge.
services.AddAuthentication(options =>
{
options.DefaultScheme = AuthenticateScheme;
options.DefaultChallengeScheme = ChallengeScheme;
})

Related

Invalid JWT bearer token on .net 6.0 web api

I am trying to implement security to my app and used Identity and JWT bearer token to authenticate but I always get the invalid token response in my swagger. Tried many solutions but I still get the same response.
[HttpPost("[action]")]
public async Task<IActionResult> Login(LoginBindingModel login)
{
IActionResult actionResult;
var user = await userManager.FindByEmailAsync(login.Email);
if (user == null)
{
actionResult = NotFound(new {errors = new[] {$"User with email {login.Email} is not found."}});
}
else if (await userManager.CheckPasswordAsync(user, login.Password))
{
if(!user.EmailConfirmed)
{
actionResult = BadRequest(new { errors = new[] { $"Email not confirmed." } });
}
else
{
var token = GenerateTokenAsync(user);
actionResult = Ok(token);
}
}
else
{
actionResult = BadRequest(new { errors = new[] { $"Password is not valid." } });
}
return actionResult;
}
private string GenerateTokenAsync(IdentityUser user)
{
IList<Claim> userClaims = new List<Claim>
{
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var x = jwtOptions.SecurityKey;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
claims: userClaims,
expires: DateTime.UtcNow.AddMonths(1),
signingCredentials: new SigningCredentials(jwtOptions.SecurityKey, SecurityAlgorithms.HmacSha256)));
}
Program.cs
using ASPNetCoreMasterAPIAssignment2.Filters;
using DomainModels;
using Infrastructure.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Repositories;
using Services;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(x =>
{
x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT token with bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
x.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
builder.Services.AddScoped<IItemService, ItemService>();
builder.Services.AddScoped<IItemRepository, ItemRepository>();
builder.Services.AddDbContext<ItemDbContext>(opt =>
{
opt.UseSqlServer(builder.Configuration.GetConnectionString("default"));
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ItemDbContext>()
.AddDefaultTokenProviders();
SecurityKey key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["jwt:secret"]));
builder.Services.Configure<JWTOptions>(_ => _.SecurityKey = key);
builder.Services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = "Bearer";
opt.DefaultChallengeScheme = "Bearer";
opt.DefaultScheme = "Bearer";
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = key
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"default": "Server=localhost;Database=EfCoreDb;Trusted_Connection=True;"
},
"jwt": {
"secret": "5a927360-790b-4ba5-bae1-09aa98364090"
}
}
when i add the [Authorized] attribute to a controller, i get the following error
invalid_token can be caused by a lot of cases.
Try to view the root cause by handling the OnChallenge method (in particular the content of context.AuthenticateFailure):
return builder.AddJwtBearer(options =>
{
options.Authority = issuer;
options.Audience = audience;
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = new System.TimeSpan(0, 0, 30)
};
options.Events = new JwtBearerEvents()
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "application/json";
// Ensure we always have an error and error description.
if (string.IsNullOrEmpty(context.Error))
context.Error = "invalid_token";
if (string.IsNullOrEmpty(context.ErrorDescription))
context.ErrorDescription = "This request requires a valid JWT access token to be provided";
// Add some extra context for expired tokens.
if (context.AuthenticateFailure != null && context.AuthenticateFailure.GetType() == typeof(SecurityTokenExpiredException))
{
var authenticationException = context.AuthenticateFailure as SecurityTokenExpiredException;
context.Response.Headers.Add("x-token-expired", authenticationException.Expires.ToString("o"));
context.ErrorDescription = $"The token expired on {authenticationException.Expires.ToString("o")}";
}
return context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = context.Error,
error_description = context.ErrorDescription
}));
}
};
});
source: https://sandrino.dev/blog/aspnet-core-5-jwt-authorization#configuring-jwt-bearer-authentication
In my case, it was resolved by updating some nuget, specially Microsoft.IdentityModel.Tokens : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1792

signalr core (2.1) JWT authentication hub/negotiate 401 Unauthorized

so I have a .net core (2.1) API that uses JWT tokens for authentication. I can login and make authenticated calls successfully.
I am using React (16.6.3) for the client, which getting a JWT code and making authenticated calls to the API works.
I am trying to add signalr hubs to the site. If I do not put an [Authorize] attribute on the hub class. I can connect, send and receive messages (its a basic chathub at the moment).
when I add the [Authorize] attribute to the class, the React app will make an HttpPost to example.com/hubs/chat/negotiate . I would get a 401 status code. the Authorization: Bearer abc..... header would be passed up.
To build the hub in React I use:
const hubConn = new signalR.HubConnectionBuilder()
.withUrl(`${baseUrl}/hubs/chat`, { accessTokenFactory: () => jwt })
.configureLogging(signalR.LogLevel.Information)
.build();
where the jwt variable is the token.
I have some setup for the authentication:
services.AddAuthentication(a =>
{
a.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
a.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = false;
options.Audience = jwtAudience;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey)),
};
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var authToken = context.Request.Headers["Authorization"].ToString();
var token = !string.IsNullOrEmpty(accessToken) ? accessToken.ToString() : !string.IsNullOrEmpty(authToken) ? authToken.Substring(7) : String.Empty;
var path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = token;
}
return Task.CompletedTask;
}
};
});
the OnMessageReceived event does get hit and context.Token does get set to the JWT Token.
I can't figure out what I am doing wrong to be able to make authenticated calls for signalr core.
solution
I updated my code to use 2.2 (not sure if this was actually required).
so I spent some time looking at the source code, and the examples within:
https://github.com/aspnet/AspNetCore
I had a Signalr CORS issue which was solved with:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed((host) => true) //allow all connections (including Signalr)
);
});
the important part being .SetIsOriginAllowed((host) => true) This allows all connections for both website and signalr cors access.
I had not added
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
I had only used services.AddAuthentication(a =>
I took the following directly from the samples in github
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}
};
Not sure if this was needed in the attribute, but the same used it on its hubs
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
with that I could not get multiple website and console apps to connect and communicate via signalr.
To be used with [Authorize] you need to set the request header. Since web sockets do not support headers, the token is passed with the query string, which you correctly parse. The one thing that's missing is
context.Request.Headers.Add("Authorization", "Bearer " + token);
Or in your case probably context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);
Example:
This is how i do it. On the client:
const signalR = new HubConnectionBuilder().withUrl(`${this.hubUrl}?token=${token}`).build();
On the server, in Startup.Configure:
app.Use(async (context, next) => await AuthQueryStringToHeader(context, next));
// ...
app.UseSignalR(r => r.MapHub<SignalRHub>("/hubUrl"));
Implementation of AuthQueryStringToHeader:
private async Task AuthQueryStringToHeader(HttpContext context, Func<Task> next)
{
var qs = context.Request.QueryString;
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"]) && qs.HasValue)
{
var token = (from pair in qs.Value.TrimStart('?').Split('&')
where pair.StartsWith("token=")
select pair.Substring(6)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
}
await next?.Invoke();
}
best method for using authorization for hubs is to force the application add the jwt token from the query string to the context
and its working for me via this method
put this inside your program.cs (dot net 6):
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
o.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (string.IsNullOrEmpty(accessToken) == false)
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
and my hubs are like this its using the main authorize method of asp
[Microsoft.AspNetCore.Authorization.Authorize]
public async Task myhub()
{
//do anything in your hub
}
youtube totorial that helped me to solve this problem

In AspBoilerPlate - Unauthorized error when calling from Angular when Windows Authentication is On

Have already raised this before and thought I have addressed it as per what suggested on THIS and THIS but seems not!
I am using ABP template (Angular and ASP .NET CORE Application) on Full .Net Framework. I simply want to use Windows Authentication to Authenticate user.
I added [Authorize] to the Authenticate in the TokenAuthController and have finally got the HttpContext.User.Identity.Name populated but only when I call the Authenticate from the Swagger (http://localhost:21021/swagger). But I am getting Unauthorized error when calling the method from Angular (login.service.ts):
POST http://localhost:21021/api/TokenAuth/Authenticate 401 (Unauthorized)
Here is the steps I have taken so far:
Changed launchSetting.json:
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:21021/",
"sslPort": 0
}
},
Added ExternalAuthenticationSource:
public class WindowsAuthSource : DefaultExternalAuthenticationSource<Tenant, User>, ITransientDependency
{
public override string Name
{
get { return "Windows Authentication"; }
}
public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
{
return Task.FromResult(true);
}
}
Added it to CoreModule:
Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<WindowsAuthSource>();
4.Adjust AuthConfigurer:
services.AddAuthentication(opt => {
opt.DefaultScheme = IISDefaults.AuthenticationScheme;
opt.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
});
Adjust StartUp.cs:
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "WINDOWS";
iis.AutomaticAuthentication = true;
});
Changed Authenticate method in the TokenAuthController:
public async Task<AuthenticateResultModel> Authenticate([FromBody]
AuthenticateModel model)
{
//var username = WindowsIdentity.GetCurrent().Name.Split('\\').Last();
var username = HttpContext.User.Identity.Name;
model.UserNameOrEmailAddress = username;
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
null
);
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new AuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
UserId = loginResult.User.Id
};
}
Sending dummy username and password from login.service.ts:
authenticate(finallyCallback?: () => void): void {
finallyCallback = finallyCallback || (() => { });
//Dummy data
this.authenticateModel.userNameOrEmailAddress = "DummyUsername";
this.authenticateModel.password = "DummyPassword";
this._tokenAuthService
.authenticate(this.authenticateModel)
.finally(finallyCallback)
.subscribe((result: AuthenticateResultModel) => {
this.processAuthenticateResult(result);
});
}

.Net Core 2.0 Web API using Identity / JWT and having user manager work with DI

I have the same problem as this:
.Net Core 2.0 Web API using JWT - Adding Identity breaks the JWT authentication
If you don't add:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityDb>()
.AddDefaultTokenProviders();
You can't Dependency inject the user manager, and sign in manager, to the Token Controller or other controllers.
Any ideas how you fix that?
I added AddDefaultTokenProviders() to services.AddIdentityCore
so my code now looks like this:
services.AddDbContext<IdentityDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>
(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
}
).AddDefaultTokenProviders();
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder
.AddEntityFrameworkStores<IdentityDb>();
//.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<IdentityRole>>();
builder.AddRoleManager<RoleManager<IdentityRole>>();
builder.AddSignInManager<SignInManager<ApplicationUser>>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "WindowLink.Security.Bearer",
ValidAudience = "WindowLink.Security.Bearer",
IssuerSigningKey = JwtSecurityKey.Create("windowlink-secret-key")
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("Member",
policy => policy.RequireClaim("MembershipId"));
});
services.AddMvc();
That did the trick for me.
Question can now be closed.

mvc6 unauthorized results in redirect instead

I have been trying to prevent the redirect when I return an NotAuthorized IActionResult from a Controller, but regardless of my attempts, NotAuthorized gets translated to a Redirect.
I have tried what is mentioned here (same issue, using older beta framework, I use 1.0.0-rc1-final). I do not have the Notifications namespace (has been removed in rc1-final).
This is my Login Controller:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return Ok(model);
}
if (result.IsLockedOut)
{
return new HttpStatusCodeResult((int)HttpStatusCode.Forbidden);
}
else
{
return HttpUnauthorized();
}
}
return HttpUnauthorized();
}
In Startup.cs I have tried variations over this:
services.Configure<CookieAuthenticationOptions>(o =>
{
o.LoginPath = PathString.Empty;
o.ReturnUrlParameter = PathString.Empty;
o.AutomaticChallenge = false;
});
Everytime a login fails (please ignore that the password is returned on Ok) and should result in an empty 401 page, I get a redirection to /Account/Login instead. What is the trick here?
The solution is not to configure CookieAuthenticationOptions directly, but do it via IdentityOptions like this:
services.Configure<IdentityOptions>(o =>
{
o.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
Taken from here ( Shawn Wildermuth --> ASP.NET 5 Identity and REST APIs --> Comment of "Mehdi Hanafi") and tested the API with Postman
config.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") &&
ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult<object>(null);
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
}
};
from Identity 2.0,
you'd need to add:
using Microsoft.AspNetCore.Authentication.Cookies;
and in ConfigureServices:
services.ConfigureApplicationCookie(options =>
{
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = (x =>
{
if (x.Request.Path.StartsWithSegments("/api") && x.Response.StatusCode == 200)
x.Response.StatusCode = 401;
return Task.CompletedTask;
}),
OnRedirectToAccessDenied = (x =>
{
if (x.Request.Path.StartsWithSegments("/api") && x.Response.StatusCode == 200)
x.Response.StatusCode = 403;
return Task.CompletedTask;
})
};
});
the segments check should of course be adjusted to your routes.
If you have some pages for which the redirect is desired and other URLs that should not have a redirect, see this question for a solution that uses the default redirect logic only for non-API URLs:
Suppress redirect on API URLs in ASP.NET Core