Net Core 6.0 + React template + OAuth 2.0 - c#

I have a test server with OAUTH 2.0 on a virtual machine (vmware).
I have created an application ASP Net Core 6.0 with React.js.
Unfortunately, in Net 6.0 in Visual Studio, the frontend and backend are divided into different servers.
In version 3.1, I could add middleware before app.UseSpa and redirect request to authorization, if the user is not logged in. Example:
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync("Blitz");
}
else
{
await next();
}
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
In Visual Studio + Net Core 6.0, I press F5 and the SPA Proxy starts and automatically redirects to frontend server.Is there a way not to redirect to frontend, but to do authorization first?
I can only do this in Net Core MVC without React template (adding React as a bundle in Index.cshtml).
This is my code on ASP Net Core 6.0 with React.js.
Program.cs
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// Add OAuth 2.0
builder.Services.AddBlitzOAuth(builder.Configuration);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync("Blitz");
}
else
{
await next();
}
});
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
}
AddBlitzOAuth
public static void AddBlitzOAuth(this IServiceCollection services, IConfiguration configuration)
{
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Blitz";
})
.AddCookie()
.AddOAuth("Blitz", options =>
{
options.ClientId = configuration["Blitz:ClientId"] ?? string.Empty;
options.ClientSecret = configuration["Blitz:ClientSecret"] ?? string.Empty;
options.CallbackPath = new PathString(configuration["Blitz:ReturnUrl"] ?? string.Empty);
options.AuthorizationEndpoint = configuration["Blitz:BlitzServer:AuthEndpoint"] ?? string.Empty;
options.TokenEndpoint = configuration["Blitz:BlitzServer:TokenEndpoint"] ?? string.Empty;
options.UserInformationEndpoint = configuration["Blitz:BlitzServer:UserInformationEndpoint"] ?? string.Empty;
var scopes = configuration["Blitz:Scopes"] ?? string.Empty;
foreach (var s in scopes.Split(" "))
options.Scope.Add(s);
options.SaveTokens = true;
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "family_name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.BackchannelHttpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
});
services.AddAuthorization();
}
I published my application ASP Net Core 6.0 with React.js in a folder and launched it. At the start, I redirected to the authorization page:
https://192.168.80.131/blitz/login/methods/password?bo=%2Fblitz%2Foauth%2Fae%3Fclient_id%3Dhttps_app%26scope%3Dprofile%2520blitz_api_user%26response_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Flocalhost%253A5432%252Fblitz-oauth%26state%3DCfDJ8HhssxjxjR1FnWtZQW8M_SqmsXoRKKwfBdcdr3N1JSmEJhDHEb13ms_FlB5CZ8jcyXbJ2tD2W6Lp5ShIaSdHMoTLgcmRfiERK2Qy3VwN90t_w1qlkqLIjfORZRKF8m748hNbU7EyLDhYW7JoXQPFJ1zad30k1x9T12aiuUR0lEkh-8oiF_otzpR7ZKhYyb57L3Z6CFDzKeoyrU6I9Ct5XMw
I logged in and got an error in a browser:
URI length exceeds the configured limit of 2048 characters from 172.18.0.2:60588
My url is too long. Why? Can I limit it?
https://192.168.80.131/blitz/oauth/ae?client_id=https_app&scope=profile%20blitz_api_user&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A5432%2Fblitz-oauth&state=CfDJ8HhssxjxjR1FnWtZQW8M_SqgTI3tKKI8frueK0vWD1rv4vEi5WZeflUuz9y9FLf8QWKw_vaf2wnfdwQEjrU6phHTMmYU29EXroVKIunPuvyEnUGn8FbJ_gRiGEWL-nI1lSA-3XvPswpW1Q2-OamOWTY5VbjErAtlwpqzh0nitPtrKKtiZNc0GcxZCg2hgDKRr0Hk7vg99lzc8enkybclaV62g2KfeMnMEJgVPmjWiM1gIhoNnaGZxIkkM7NhmSQ6O-RXhqoBdcHGvukni9hdXwCjTvjgObJDM74ESta-dsAH2rkk1Xzjo8i8ZLXO7xAWYOuXMlCqoT3z2g0VYHBVR2l0sxzw-Iorb1VuSDksQ7NyXTDKW-wP0Deohz6JA0PwEbhGmMm5vcpPrPcyER4Tu7vLYoZxKv2Yh7VP2j5PSJpzQMhlGUmcs4tiq9G1fddb_wgNBQShyKPrS9bdPY8M6poje-aZseY_1w27iRiaa-9eg4w5sI_zeSdsqg4OwXFGLDlPOpn79h9GCHrSP20VknWvddPlIy-VF2c9O3NIotDkKxyTcfy-1qmMmD_xQpGi0K2tNlu6ke8VbUT488oFAYXSYWpzePIde1SjXoO0FiikZamfzBAnt-ISTVwgp5_R2A1oPpaqkuKNidQtDLYPat9sCQeG08UU9y79qhddQTejxeCrJsZx35Fie-bhZpMlABHqwxwcDGVnLd4vcbDrpqsLrcf9bOHHSRCUekKPlOYeO7VvFOhIDFVPk3I6sKxX22WEXaAL5V9fqXzW-vhFlRsH64uczcVfCgAPfPrhoQJVJOGAxRW8bkg_v7yqxYqwTbAYZ77xjTLwMsZtbOysPUug7Qnelwo6ZHN9AVgdqgGS0n2kLZ7zxhOkSaAokK0FmOE5UeM2qfZMZ2CtfpbLyceW_0DLjoX99GK8m7kSOKE0vykcXNkPOQ24Es830851FNd7PJ4TsNRd7TYmvx-T9sh1UTSpeEenPIR9ST4B001fe6ZrgwjMKxDQsvdwFlgxyFbN-Sa5RDfGwVZsCYxT38R6cdaeDBBaaSnyT7UcDsz6NP8H2sZImFTKF64jw1vaTjz_3kQrKJuay_IqNhFQG30JeclB2LpEKDa5Cek3Q0u5kNhdJ33QnYrm7SBugx2S1CuR9CPkJPFc78dU7BAnusPRPWTuKiPfC-OfLDTYAzkzkgfr-kRuEcZ-sgURnhH5drfMfjftmW92dL3F_rVIN0wluLoNp7-l2YUvOdoZNSqRf_zmlTMkhC5lBLC-x0ab5TWIVUOXxVppna9HSVpwjBd7TbZ7dy5Lu9Sb1McZsGPa0HgF5npgIxS2X4XZBE6xV_cnuXGOHGeGMmdSQ8229PmYTuZz10JL5Wd9lPSpXyH9laFr9R2F7p4xSZWUzMI4qGSfvjOJfU607H4pQalPtTaOdXDY4V_-JLPWHPX4kL64GWMz3ympljtmYbSPB21iUSNJkNGRbY88SJ_6l0IBaA3_mmAXg-pBQCuiCPbDDY9IIEOvII-2-3v9i8OdXsTDBgeLaz5XgD5NnBu9Px6IHxVjRfd7WNVqKzI57BWAmBngJvOFS4avcgr-dtyNRhc9VmpnQEGW3nYH2WFbRgJo9Xi_6xcohEzQj-97LfKSjWm1v_VZOCPn3_HNA-IM1qS3UtsWFRADwedeJd5v8QVe_wjQleIe-KlRoaD1TeuisrxRDZUfvCnwaTUeyUjyfM4Tquhb3gqJan1MLEu2MMo7fG-98ZUKNs-1jYSA5oTad-i3ZarRHGuf2zgZbVul9QyPPULu9fk-1y7uY2-FSbCiz5tsTa-NaIwnsa0CNHEOVwtoXB8b_V5PocCNR2nvT_vQwrnD-r-_z73ZKXAko1aXdoftnte_S71XLVz1MfP478ReFAGzb2VysE5Z0T4M1X_108WZoiiTkI4G7Uv0IjbeHa3KqKrPiWghG_Xdb0Nmyzn6NWh2WzkgvN7oCCL16n-7NcJrtMidDQgH-ODHIwCNmkJjSvYLvEXMH89AqpVdnl9-aQEXvKux8uTQoj8pBm9rddIT4OygnwkrIpqSk36fueTlT3DrLvKv1tgfiV3xZAI7Baq5eKCn3aB2UcAEZM9wpekWdDK5f5uUpsIsQp6J0lY
I think it is possible to solve the problem using Net Core MVC template and adding react in Index.cshtml as a bundle. But I would like to use Net Core 6.0 with React template.

Related

May i implement Identity Server 4 Authentification with JWT token and MVC Client using IS4 template?

I have a problem with Identity Server. Recently i got a task on an internship to implement the Identity Server 4 authentification, so first of all I decided to connect IS4 with default MVC client. I figured out how to do it with cookies, as they are used by default (turned out it was easy enough if using templates). But then I got task to use JWT tokens. And that's where I have a problem. All tutorials say that I have to rewrite my IS4 project from the very beginning, but is there an opportunity to use the IS4 template?
Identity server startup file using cookies(by default .Net 3.1 is used)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
// register your IdentityServer with Google at https://console.developers.google.com
// enable the Google+ API
// set the redirect URI to https://localhost:5001/signin-google
options.ClientId = "copy client ID from Google here";
options.ClientSecret = "copy client secret from Google here";
});
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
IS Config.cs file
public static IEnumerable<ApiResource> GetApiResources()
{
yield return new ApiResource("SwaggerAPI");
yield return new ApiResource("OrdersAPI");
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
yield return new IdentityResources.OpenId();
yield return new IdentityResources.Profile();
}
/// <summary>
/// IdentityServer4 version 4.x.x changes
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiScope> GetApiScopes()
{
yield return new ApiScope("SwaggerAPI", "Swagger API");
yield return new ApiScope("blazor", "Blazor WebAssembly");
yield return new ApiScope("OrdersAPI", "Orders API");
}
public static IEnumerable<Client> GetClients() =>
new List<Client>
{
new Client
{
ClientId = "add_mvc",
ClientSecrets = { new Secret("add_mvc_secret".ToSha256()) },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes =
{
"OrdersAPI",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
RedirectUris = {"https://localhost:7272/signin-oidc"},
PostLogoutRedirectUris = {"https://localhost:7272/signout-callback-oidc"},
RequireConsent = false,
AccessTokenLifetime = 5,
AllowOfflineAccess = true
// AlwaysIncludeUserClaimsInIdToken = true
}
};
MVC client program.cs file(.NET 6.0)
builder.Services.AddAuthentication(config =>
{
config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, config =>
{
config.Authority = "https://localhost:5001";
config.ClientId = "add_mvc";
config.ClientSecret = "add_mvc_secret";
config.SaveTokens = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
config.ResponseType = "code";
config.Scope.Add("OrdersAPI");
config.Scope.Add("offline_access");
config.GetClaimsFromUserInfoEndpoint = true;
config.ClaimActions.MapJsonKey(ClaimTypes.DateOfBirth, ClaimTypes.DateOfBirth);
});
builder.Services.AddAuthorization(config =>
{
config.AddPolicy("HasDateOfBirth", builder =>
{
builder.RequireClaim(ClaimTypes.DateOfBirth);
});
});
builder.Services.AddHttpClient();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
I also tried just to add JWT configuration in IS4 startup.cs file, but the error said that jwt tokens can't by used with some methods from signinmanager.
A best practice is to implement and host IdentityServer, Client and the API in separate services. By separating them, it will be much easier to debug and reason about. The client should receive an access token that it then can use to call an API using the HttpClient library.
You do not need to add AddJwtBearer to your client or IdentityServer, instead you add that to your separate API project. The main purpose of .AddJwtBearer is to verify and convert the incoming access token into ClaimsPrincipal user.
You can use this code in the Client to get access to to the various tokens:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string idToken = await HttpContext.GetTokenAsync("id_token");
string refreshToken = await HttpContext.GetTokenAsync("refresh_token");
string tokenType = await HttpContext.GetTokenAsync("token_type");
string accessTokenExpire = await HttpContext.GetTokenAsync("expires_at");
Then you can use the access token to call the API using the HttpClient library.

Securing a SPA by Identityserver 6 before first load in .NET 6

I am trying to implement authentication server using Identity Server 6 and trying to use this to authenticate multiple SPA applications with different domains. I have tested with .NET 3.1 and Identity server 4 and I can redirect to login page of the authentication server when accessing the spa for the first time but same thing is not working in .NET 6 and Identity Server 6. Spa is loading with home page initially without redirecting to login page and when I access the resource with [Authorize] attribute I am getting 200 status with login url as a response but it is not redirecting to login page.
I have followed this What is the right way to Securing a SPA by authorization server before first load using ASP.NET Core 3.0? but could not make it work with the latest vesrions.
So far the code looks as below on SPA angular app.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Serilog;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateBootstrapLogger();
Log.Information("Starting up");
try
{
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}")
.Enrich.FromLogContext()
.ReadFrom.Configuration(ctx.Configuration));
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(authenticationOptions =>
{
authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie("cookie")
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, openIdConnectOptions =>
{
openIdConnectOptions.Authority = builder.Configuration["InteractiveServiceSettings:AuthorityUrl"];
openIdConnectOptions.ClientId = builder.Configuration["InteractiveServiceSettings:ClientId"];
openIdConnectOptions.ClientSecret = builder.Configuration["InteractiveServiceSettings:ClientSecret"];
openIdConnectOptions.Scope.Add(builder.Configuration["InteractiveServiceSettings:Scopes:0"]);
openIdConnectOptions.GetClaimsFromUserInfoEndpoint = true;
openIdConnectOptions.ResponseType = "code";
openIdConnectOptions.SaveTokens = true;
openIdConnectOptions.UsePkce = true;
openIdConnectOptions.ResponseMode = "query";
openIdConnectOptions.RequireHttpsMetadata = false;
});
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowAll");
app.Use(async (context, next) =>
{
if (!(context.User.Identity.IsAuthenticated && context.Request.Path != "/signin-oidc"))
{
await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
else
{
await next();
}
});
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllers();
// //fallback to SPA
// //endpoints.MapFallbackToFile("index.html");
// endpoints.MapFallback(CreateRequestDelegate(endpoints, "index.html"));
// //local function
// RequestDelegate CreateRequestDelegate(IEndpointRouteBuilder endpoints, string filePath)
// {
// var app = endpoints.CreateApplicationBuilder();
// //Use Cookie authentication to secure angular index.html file
// app.Use(async (context, next) =>
// {
// bool isAuth = false;
// //TODO: run logic to check authentication
// //....
// if (!isAuth)
// {
// await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
// }
// else
// {
// await next.Invoke();
// }
// });
// //to SPA index.html
// app.Use(next => context =>
// {
// context.Request.Path = "/" + filePath;
// // Set endpoint to null so the static files middleware will handle the request.
// context.SetEndpoint(null);
// return next(context);
// });
// app.UseStaticFiles();
// return app.Build();
// }
//});
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Unhandled exception");
}
finally
{
Log.Information("Shut down complete");
Log.CloseAndFlush();
}

"There was an error trying to log you in: '' " Blazor WebAssembly using IdentiyServer Authentication

I have a Blazor WebAssembly app using IdentityServer that comes with the template as my authentication service. I am having an issue where some users are seeing "There was an error trying to log you in: ''" once they try to login. I had users clear cookies and cache, and they are still experiencing this problem in all their browsers. The weird thing is that most users are able to login, but only a small percent is getting that error. Another odd thing is that it seems like if they use another device such as a phone, another pc, or ipad it works. What could be causing this issue? I have been having trouble trying to debug this issue as I am not able to replicate it on my end and so far not seeing any logs to get any info.
This app is hosted in Google Cloud Platform using linux Docker container.
Thank you in advance
Edit: Here is my startup class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
private const string XForwardedPathBase = "X-Forwarded-PathBase";
private const string XForwardedProto = "X-Forwarded-Proto";
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://www.fakedomainexample.com",
"https://fakedomainexample.com");
});
});
services.AddHttpContextAccessor();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(
Configuration.GetConnectionString("ConnectionString")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// For some reason, I need to explicitly assign the IssuerUri or else site gets invalid_issuer error
services.AddIdentityServer(x => x.IssuerUri = "https://www.fakedomainexample.com").AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 8;
options.Password.RequiredUniqueChars = 1;
// User settings.
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedAccount = false;
});
// Added Cookie options below to fix an issue with login redirect in Chrome for http
// https://stackoverflow.com/questions/60757016/identity-server-4-post-login-redirect-not-working-in-chrome-only
// This one worked: https://stackoverflow.com/questions/63449387/cannot-redirect-back-to-angular-client-after-login-in-identity-server
services.ConfigureExternalCookie(option =>
{
option.LoginPath = "/Account/Login";
option.Cookie.IsEssential = true;
option.Cookie.SameSite = SameSiteMode.Lax;
});
services.ConfigureApplicationCookie(option =>
{
option.LoginPath = "/Account/Login";
option.Cookie.IsEssential = true;
option.Cookie.SameSite = SameSiteMode.Lax;
});
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseRewriter(new RewriteOptions()
.AddRedirectToWwwPermanent());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.Use((context, next) =>
{
if (context.Request.Headers.TryGetValue(XForwardedPathBase, out StringValues pathBase))
{
context.Request.PathBase = new PathString(pathBase);
}
if (context.Request.Headers.TryGetValue(XForwardedProto, out StringValues proto))
{
context.Request.Scheme = proto;
}
//context.SetIdentityServerOrigin("https://www.fakedomainexample.com");
return next();
});
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
const string cacheMaxAge = "3600";
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Add("Cache-Control", $"public, max-age={cacheMaxAge}");
}
});
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Lax,
});
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
We also faced this problem. The error appears if the server time does not match the client time. Experiments have shown that it is enough to have a difference of 10 minutes.
Ideally, the time on the client and server should be in sync.
We are currently asking customers to check the time on the device, but this is not a solution to the problem.
Who have this problem when publishing on IIS with a self-signed certificate, it may be caused by auth of Application Pool on read the Certificate.
For solve this problem, open certification manager, right click on the certificate, all tasks, private key manager and insert IIS group (IIS_IUSRS) for read/write permission. For test, try "Everyone".
Had this issue when using Blazor WASM hosted by ASP.NET 6 application and configured OIDC with Openiddict.
Issue was solved by remapping UserOption for OpenIddict default claims.
// Blazor WASM Program.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-6.0&tabs=visual-studio
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Oidc", options.ProviderOptions);
// Set mapping for claims fixed issue
options.UserOptions.NameClaim = "name";
options.UserOptions.RoleClaim = "role";
options.UserOptions.ScopeClaim = "scope";
});
await builder.Build().RunAsync();
You need SameSite as None for OpenID Connect to work in this case. Also you need to have HTTPS.
Read more https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
I believe you have to configure you Identity on appsettings.json
On dev you have
"IdentityServer": {
"Clients": {
"updown": {
"Profile": "IdentityServerSPA"
}
},
"Key": {
"Type": "Development"
}
But in production you must configure some certificates.
"Key": {
"Type": "File",
"FilePath": "path to .pfx",
"Password": "pass"
}
otherwise not recommended put the tempkey.json from obj folder
Got the same error when using Cognito UserPools with OIDC. The reason for me was, the profile checkbox was not selected in the App Client Settings.
With the version of .NET 7, to fix this error "there was an error trying to log you in 500" - you need to apply migrations and create a database - otherwise, this out-of-the-box solution won't work.

Publishing web api with swagger on IIS

I am trying figure out how to publish a .net core 3 API with Swagger (SwashBuckle) after following this example documentation . So it works locally and when I hit F5 IIS Express launches the site under http://localhost:8033/index.html
Here is the Configure code in startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(env.ContentRootPath),
RequestPath = new PathString("")
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
c.DocumentTitle = "TestAPI";
c.DocExpansion(DocExpansion.None);
c.RoutePrefix = string.Empty;
});
}
Next I published the API to a local folder and copied the files to the IIS folder on the server. If I open the server API domain I get a page can’t be found. Which address should I use to open up the swagger UI on the server? Is something missing from the configuration?
There is no problem with your Swagger settings. Please don’t forget configure the Swagger generator, as well as the comments path for the Swagger JSON.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = new Uri("https://twitter.com/spboyer"),
},
License = new OpenApiLicense
{
Name = "Use under LICX",
Url = new Uri("https://example.com/license"),
}
});
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
Besides, Please ensure that the server has installed the Asp.net core hosting bundle on the sever-side.
https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-aspnetcore-3.1.6-windows-hosting-bundle-installer
Feel free to let me know if there is anything I can help with.
Assuming we're using ASP.NET Core / 7 for building the web api.
For .NET 7 (or minimal api) we need to try commenting / adjusting the snippet below in Program.cs
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
Next build / publish using VS / VS Code
And access your API on local dev / test:
https://local-api.{yur-api-name}.com/{service-name}Services/swagger/index.html
Just to add, it could also be possible that the following nodes are missing from the API project file under the PropertyGroup node.
<RuntimeIdentifiers>win10-x64;</RuntimeIdentifiers>
<AspNetCoreModuleName>AspNetCoreModuleV2</AspNetCoreModuleName>
Edit your Startup.cs File
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler("/Error/index.html");
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger(c =>
{
c.RouteTemplate = "swagger/{documentName}/swagger.json";
});
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger";
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
// custom CSS
c.InjectStylesheet("/swagger-ui/custom.css");
});
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 204)
{
ctx.Response.ContentLength = 0;
}
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseCors();
}
https://youtu.be/6HiaXCAlRr0

Redirect to login when unauthorized in ASP.NET Core

In the previous ASP.NET MVC, there was an option to redirect to the login action, if the user was not authenticated.
I need the same thing with ASP.NET Core, so I:
created a ASP.NET Core project from the Visual Studio template
added [Authorize] to some arbitrary action
opened the corresponding view in my browser
I don't expect a redirect because I haven't configured it. BUT, it automatically redirects to the login action!
Where/how is this option set?
With the current aspnet core version (2.1.0), this has changed, now you can use the extensions:
services.ConfigureApplicationCookie(options => options.LoginPath = "/login");
or
services
.AddAuthentication()
.AddCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/logout";
});
You can see more about migrating in to 2.0 in this article.
The redirect did not work in my app at all and none of the solutions here fixed it, but using Status Code Pages did:
app.UseStatusCodePages(async context =>
{
var response = context.HttpContext.Response;
if (response.StatusCode == (int)HttpStatusCode.Unauthorized ||
response.StatusCode == (int)HttpStatusCode.Forbidden)
response.Redirect("/Authentication");
});
app.UseMvc(...
You can configure the path using CookieAuthenticationOptions class.
Something like this.
app.UseCookieAuthentication(new CookieAuthenticationOptions {
LoginPath = new PathString("/Login/"),
AuthenticationType = "My-Magical-Authentication",
// etc...
},
});
Here is the updated link for CookieAuthenticationHandler
For anyone that's interested it can also be done with the AddIdentity service provider.
services.AddIdentity<User, IdentityRole>(options =>
{
options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
options.Cookies.ApplicationCookie.AutomaticChallenge = true;
options.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
})
.AddEntityFrameworkStores<MehandiContext>()
.AddDefaultTokenProviders();
And as explained here: https://stackoverflow.com/a/41643105/5784635
I attempted this in April 2017 and "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0" doesn't redirect I had to use the 1.0.1 version
The way that dotnet core scaffolds Cookie Authentication is using the Identity framework. For a fresh project, I recommend going to the command line and doing something like this:
dotnet new mvc -o ExampleProject --auth Individual
You can gain full control of the authentication process by modifying the folowing method in Startup.cs to look like this:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
// services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
}
Reference:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.2&tabs=visual-studio#full
My personal preference for authentication is the hybrid flow of IdentityServer4, gives you a scope for configuring multiple applications using a single sign on.
this code block in the startup file works for me in .Net Core 3.1
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
placing should be important in configuration middleware piplines.
app.UseSession();
app.UseAuthentication();
app.UseStatusCodePages(context => {
var response = context.HttpContext.Response;
if (response.StatusCode == (int)HttpStatusCode.Unauthorized ||
response.StatusCode == (int)HttpStatusCode.Forbidden)
response.Redirect("/Login");
return Task.CompletedTask;
});
app.UseClaimsMiddleware();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Login}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
The code above works fine for me using Identity Authentication in asp net core 3.1
First you must add the following code to your Startup.cs file
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = ".AspNetCore.Identity.Application";
options.AccessDeniedPath = "/User/PageNotAllowed";
});
Create an Action in your Controller responsable for manage the user account (In my case is the User class)
public IActionResult PageNotAllowed()
{
return View();
}
Final step you just need to create the PageNotAllowed View at your own taste.
The reason why application knows where the login page is because by default "Login" page supposed to be placed in "Account" folder and page supposed to be called "Login" so, like "Account/Login"
So, if you change the "Account" folder to something else like "AccountFolder" then you will get a http 404 as page not found.
To explicitly specify where the login page is go to "Program.cs" file add the following "LoginPath" defination.
builder.Services.AddAuthentication().AddCookie("YourCookieName", options =>
{
options.Cookie.Name = "YourCookieName";
options.LoginPath = "/Account/Login";
});
The above example is from .NET 6

Categories