AzureSignalR with Xamarin - NegotiateAsync response 401 (Unauthorized) - signalr

I am facing an issue that /nagotiate request fails with 401(Unauthorized).
Server
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options => {
options.EnableDetailedErrors = true;
})
.AddAzureSignalR(options =>
{
options.InitialHubServerConnectionCount = 1;
options.ConnectionString = "xxxx"
});
}
protected virtual void ConfigureAuthentication(IServiceCollection services)
{
services
.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = $"{AzureB2CConfig.Instance}/{AzureB2CConfig.Domain}/{AzureB2CConfig.SignUpSignInPolicyId}/v2.0/";
options.Audience = AzureB2CConfig.Audience;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var authToken = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(authToken) &&
(path.StartsWithSegments("/myhubs")))
{
context.Token = authToken;
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = false
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("AllowedUser", policy =>
{
policy.Requirements.Add(new AllowedUserRequirement());
});
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseFileServer();
app.UseEndpoints(routes =>
{
routes.MapHub<MyHub>($"/myhubs");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Client
var connection = new HubConnectionBuilder()
.WithUrl(SignalRServerUrl, options =>
{
options.AccessTokenProvider = authenticationService.GetAccessToken; // returns valid access token
})
.WithAutomaticReconnect()
.Build();
await Policy
.Handle<Exception>()
.WaitAndRetryAsync(1, x => TimeSpan.FromMilliseconds(500))
.ExecuteAsync(() => _connection.StartAsync()); // causes 401 Unauthorized
I have tried many things but none of them helped.
I have read a lots of articles and here is example. this, this, this, this, this, this,this, this and have spend few days for finding the solution but could not..
Are there any missing configuration?
FrameWork: .Net6
Updated at 20th Oct 2022
I Added services.AddAuthorization() parts and Authentication is now success between the app and the server. But app connects to Azure SignalR. This Azure SignalR authentication is failing. 401

Related

.NET 6.0 JWT token is not recognized from localhost, but it IS recognized through SwaggerUI-- what's up?

Problem
I have a Target Framework: .NET 6.0 API backend with an exposed API with Identity Framework implemented in it.
I can successfully obtain data 0 issue on unauthorized API Endpoints with this above them on both SwaggerUI AND my localhost frontend. [AllowAnonymous]
When it comes to authorized API Endpoints however it is a totally different story.
SwaggerUI has NO PROBLEM AT ALL when I put my generated "JWT" into the "Authorize" padlock on the side of the screen.
Localhost JWT are simply not accepted or seen or understood or something I totally don't understand.
Request seems fine
Screenshot of the API request I am sending over has the authorization with the token, I also took the token to JWT io and it can parse out the name without problem.
I've also tried removing or adding "bearer" to the authorization header sent over and that makes no difference.
Screencap of request:
https://i.imgur.com/CY7F32o.png
My entire Startup.cs
(I know showing anything less would make it harder for you all to help me so >_< )
namespace API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(opt =>
{
opt.UseLazyLoadingProxies();
opt.UseMySql(Configuration.GetConnectionString("DefaultConnection"), ServerVersion.AutoDetect(Configuration.GetConnectionString("DefaultConnection")), opt => opt.EnableRetryOnFailure());
});
services.AddIdentity<UserModel, IdentityRole>(options =>
{
//options.Password.RequiredLength = 5;
//options.Password.RequireLowercase
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<DbContext>()
.AddDefaultTokenProviders();
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "auth_cookie";
options.Cookie.SameSite = SameSiteMode.None;
options.LoginPath = new PathString("/api/contests");
options.AccessDeniedPath = new PathString("/api/contests");
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.Configure<SmtpSettings>(Configuration.GetSection("SMTP"));
services.AddSingleton<IEmailManager, EmailManager>();
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
//Allow localhost to actually contact the server.
services.AddCors(opt =>
{
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("WWW-Authenticate")
.WithOrigins("http://localhost:3000", "http://localhost:5000", "https://localhost:5000")
.AllowCredentials();
});
});
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
//"Normal" API Auth.
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = true, //~5 minute leeway?
ClockSkew = TimeSpan.Zero //force time.
};
//SignalR auth.
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chat"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
}).AddDiscord(options =>
{
options.CorrelationCookie.SameSite = SameSiteMode.Lax;
options.ClientId = Configuration["Discord:ClientId"];
options.ClientSecret = Configuration["Discord:ClientSecret"];
options.Scope.Add("email");
//options.CallbackPath = "/";
});
services.AddScoped<IJwtGenerator, JWTGenerator>();
services.AddScoped<IUserAccessor, UserAccessor>();
services.AddScoped<IImageAccessor, ImageAccessor>();
services.Configure<Infrastructure.Images.CloudinarySettings>(Configuration.GetSection("Cloudinary"));
services.AddRazorPages().AddRazorRuntimeCompilation();
services.AddServiceLayer();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
});
app.UseCors(c => c.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToController("Index", "Fallback");
});
}
}
}

ASP.NET SignalR - 401 Unauthorized - JWT Authorization

I'm trying to implement SignalR support in an ASP.NET / Angular App, but I'm having difficulties with the JWT-Authentication.
First I'm appending the token in the appropriate request, which can be seen in the server code down below.
This seems to work fine, but I always get HTTP error 401 (Unauthorized) when trying to establish the websocket connection. When hitting a breakpoint in the code above, I can see that the token is assigned correctly:
All the other HTTP endpoints have the [Authorize] annotation, where the User object is accessible as excepted.
Here's a screenshot of the HTTP traffic. It's the same bearer token that works for all the other endpoints, but here it says "invalid token":
The server uses the Microsoft.AspNetCore.SignalR package, Version 1.1.0
Angular Client uses "#microsoft/signalr": "^5.0.7"
Here's the client code where the token is added when establishing the connection:
public startConnection = () => {
const options: IHttpConnectionOptions = {
accessTokenFactory: () => {
return this._settings.authorization?.access_token;
}
};
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:8990/apphub', options)
.build();
this.hubConnection
.start()
.then(() => console.log('Connection started'))
.catch(err => console.log('Error while starting connection: ' + err))
}
This is the startup code to add JWT authentication:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = ctx =>
{
Log.Logger.Information("Authentication - Token validated.");
return Task.CompletedTask;
},
OnMessageReceived = ctx =>
{
Log.Logger.Information("Authentication - Message received.");
var tokenInQuery = ctx.Request.Query.TryGetValue("authorization", out var queryToken);
var tokenInHeader = ctx.Request.Headers.TryGetValue("authorization", out var headerToken);
if (tokenInQuery || tokenInHeader)
{
var path = ctx.HttpContext.Request.Path;
if (path.StartsWithSegments("/apphub", StringComparison.OrdinalIgnoreCase))
{
if(!string.IsNullOrEmpty(queryToken))
{
ctx.Token = queryToken;
}
else if(!string.IsNullOrEmpty(headerToken))
{
ctx.Token = headerToken;
}
}
}
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
Log.Logger.Information("Authentication - Authentication failed.");
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["SecurityKeyIssuer"],
ValidAudience = Configuration["SecurityKeyAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"]))
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMemoryCache();
// services.AddCors();
services.AddCors(o => o.AddPolicy("AllowAnyOrigin", builder =>
{
builder.WithOrigins("http://locahost:4000")
//.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed((x) => true)
.AllowCredentials();
}));
Any ideas?

no authentication handler is configured to authenticate for the scheme: "bearer" .net core 2.0

I am new to .net Core, I am trying to upgrade a project from .net Core 1.0 to 2.0,
when I am trying to access the API I am getting this error.
"no authentication handler is configured to authenticate for the scheme: "bearer" .net core 2.0".
As UseJwtBearerAuthentication doesnt work in .net core 2.0 I replacing it with AddAuthentication.
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
app.UseAuthentication();
app.UseCors("AllowAll");
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var tvp = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "ABC",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "User",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.FromMinutes(5)
};
services.AddSingleton(s => tvp);
ConfigureAuth(services, tvp);
}
private void ConfigureAuth(IServiceCollection services, TokenValidationParameters tvp)
{
//TODO: Change events to log something helpful somewhere
var jwtEvents = new JwtBearerEvents();
jwtEvents.OnAuthenticationFailed = context =>
{
Debug.WriteLine("JWT Authentication failed.");
return Task.WhenAll();
};
jwtEvents.OnChallenge = context =>
{
Debug.WriteLine("JWT Authentication challenged.");
return Task.WhenAll();
};
jwtEvents.OnMessageReceived = context =>
{
Debug.WriteLine("JWT Message received.");
return Task.WhenAll();
};
jwtEvents.OnTokenValidated = context =>
{
Debug.WriteLine("JWT Message Token validated.");
return Task.WhenAll();
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.TokenValidationParameters = tvp;
o.Events = jwtEvents; });
}
Under Configure method I have:
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCors("AllowAll");
app.UseRequestResponseLogging();
app.UseNoCacheCacheControl();
app.UseMvc();
AuthController.cs
[HttpPost]
[EnableCors("AllowAll")]
[AllowAnonymous]
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Authenticate([FromBody] UserContract model)
{
}
AuthenticationMiddleware:
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
public AuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IAuthUser authUser)
{
if (context.User?.Identity != null)
{
if (context.User?.Identity?.IsAuthenticated == true)
{
authUser.Username = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
}
using (LogContext.PushProperty("Username", authUser.Username))
{
await _next.Invoke(context);
}
}
}
You can use AddJwtBearer method , please refer to below article for how to use extension :
https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide
Code sample below for AddJwtBearer with options and events is for your reference :
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("Bearer",options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "Issuer",
ValidAudience = "Audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Yourkey"))
};
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
var loggerFactory = context.HttpContext.RequestServices
.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("Startup");
logger.LogInformation("Token-Expired");
context.Response.Headers.Add("Token-Expired", "true");
}
return System.Threading.Tasks.Task.CompletedTask;
},
OnMessageReceived = (context) =>
{
return Task.FromResult(0);
}
};
});
And use on controller/action like :
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Don't forget to enable authentication in Configure method :
app.UseAuthentication();

I am doing everything as always to enable CORS,BUT THIS TIME DOES NOT WORK

Before i always used this approach to enable cors in asp.net web api core and it was with success.But this time i am doing the same thing,but it does not work.I dont understand what is happening.Services.add cors is before services.addmvc and i use the cors middleware in Configure method before i use the mvc.Angular is running on localhiost 4200.And the strange things is that two days ago it worked.Please help
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
services.AddDbContext<AuthenticationContext>(x =>
{
x.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"));
});
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AuthenticationContext>();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 4;
}
);
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:4200");
});
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//Jwt Authentication
var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => {
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 204)
{
ctx.Response.ContentLength = 0;
}
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "StaticFiles")),
RequestPath = new PathString("/StaticFiles")
});
app.UseAuthentication();
//app.UseHttpsRedirection();
app.UseMvc();
}
}
put following in ConfigureServices method
services.AddCors();
and insert following in Configure method
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
if error persists then with origin as below:
app.UseCors(builder => builder
.WithOrigin("http://localhost:4200/")
.AllowAnyMethod()
.AllowAnyHeader());

How to remove the redirect from an ASP.NET Core webapi and return HTTP 401?

Following the answer on this question, I have added authorization on everything by default, using the following code:
public void ConfigureServices(IServiceCollection aServices)
{
aServices.AddMvc(options =>
{
var lBuilder = new AuthorizationPolicyBuilder().RequireAuthenticatedUser();
var lFilter = new AuthorizeFilter(lBuilder.Build());
options.Filters.Add(lFilter);
});
aServices.AddMvc();
}
public void Configure(IApplicationBuilder aApp, IHostingEnvironment aEnv, ILoggerFactory aLoggerFactory)
{
aApp.UseCookieAuthentication(options =>
{
options.AuthenticationScheme = "Cookies";
options.AutomaticAuthentication = true;
});
}
However when someone tries to access something unauthorized, it returns a (what seems a default) redirect URL (http://foo.bar/Account/Login?ReturnUrl=%2Fapi%2Ffoobar%2F).
I want it to return a HTTP 401 only, instead of a redirect.
How can I do this in ASP.NET 5 for a WebAPI?
I had with this problem in an Angular2 + ASP.NET Core application. I managed to fix it in the following way:
services.AddIdentity<ApplicationUser, IdentityRole>(config => {
// ...
config.Cookies.ApplicationCookie.AutomaticChallenge = false;
// ...
});
If this is not working for you, you can try with the following method instead:
services.AddIdentity<ApplicationUser, IdentityRole>(config => {
// ...
config.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api"))
{
ctx.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
// added for .NET Core 1.0.1 and above (thanks to #Sean for the update)
ctx.Response.WriteAsync("{\"error\": " + ctx.Response.StatusCode + "}");
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
// ...
}
Update for Asp.Net Core 2.0
Cookie options are now configured in the following way:
services.ConfigureApplicationCookie(config =>
{
config.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx => {
if (ctx.Request.Path.StartsWithSegments("/api"))
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else {
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
});
By the url you get redirected to I assume you're using cookie authentication.
You should get the desired results by setting the LoginPath property of the CookieAuthenticationOptions to null or empty as described by one of the users.
app.UseCookieAuthentication(options =>
{
options.LoginPath = "";
});
It was probably working back then but it's not working anymore (because of this change).
I've submitted a bug on GitHub for this.
I'll update the answer once it gets fixed.
I had a similar problem.
I solved this adding by manually the services.
ConfigureServices method:
services.AddTransient<IUserStore<User>, UserStore<User, IdentityRole, ApplicationDbContext>>();
services.AddTransient<IPasswordHasher<User>, PasswordHasher<User>>();
services.AddTransient<IUserValidator<User>, UserValidator<User>>();
services.AddTransient<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.AddTransient<IPasswordValidator<User>, PasswordValidator<User>>();
services.AddTransient<IdentityErrorDescriber, IdentityErrorDescriber>();
services.AddTransient<ILogger<UserManager<User>>, Logger<UserManager<User>>>();
services.AddTransient<UserManager<User>>();
services.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors(options=> {
options.AddPolicy("AllowAllHeaders", (builder) => {
builder.WithOrigins("*").AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("WWW-Authenticate"); ;
});
});
services.AddAuthentication(options=> {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
options.ApiSecret = "secret";
});
Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("AllowAllHeaders");
app.UseAuthentication();
app.UseMvc();
}
I am using aspnet core 2.0, IdentityServer 4 and aspnet identity.
Setting LoginPath = "" or null no longer works on Version 1.1.0.0. So here's what I did:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
ExpireTimeSpan = TimeSpan.FromDays(150),
AuthenticationScheme = options.Cookies.ApplicationCookie.AuthenticationScheme,
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync,
OnRedirectToLogin = async (context) => context.Response.StatusCode = 401,
OnRedirectToAccessDenied = async (context) => context.Response.StatusCode = 403
},
AutomaticAuthenticate = true,
AutomaticChallenge = true,
});
Be aware, you should not use the CookieAuthentication only if you want to use your own Authentication Mechanism for example bypassing the Identity provider which not the case for most of us.
The default Identity provider use the CookieAuthenticationOptions behind the scene, you can configure it like the below.
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireUppercase = false;
o.Password.RequireLowercase = false;
o.Password.RequireNonAlphanumeric = false;
o.User.RequireUniqueEmail = true;
o.Cookies.ApplicationCookie.LoginPath = null; // <-----
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Tested in version 1.0.0
in case it helps, below is my answer - with dotnet 1.0.1
its based on Darkseal's answer except I had to add the line ctx.Response.WriteAsync() to stop the redirect to the default 401 URL (Account/Login)
// Adds identity to the serviceCollection, so the applicationBuilder can UseIdentity
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
//note: this has no effect - 401 still redirects to /Account/Login!
//options.Cookies.ApplicationCookie.LoginPath = null;
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
//for WebApi: prevent aspnet core redirecting to 'Account/Login' on a 401:
if (ctx.Request.Path.StartsWithSegments("/api"))
{
ctx.RedirectUri = null;
ctx.Response.WriteAsync("{\"error\": " + ctx.Response.StatusCode + "}");
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
})
.AddDefaultTokenProviders();
}
Use this code in Startup :
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Account/Login";
options.LogoutPath = $"/Account/Logout";
options.AccessDeniedPath = $"/Account/AccessDenied";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
ctx.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
ctx.Response.StatusCode = 403;
return Task.CompletedTask;
}
};
});

Resources