Error with Google Login and ASP.NET Core Web API - asp.net-core

I have a Blazor client with a Asp.net Core Web API using a custom implementation of JWT for authentication and I'm trying to implement an external identity provider (Google OAuth).
From Blazor when a user clicks the link to auth with google, is calling the following API endpoint:
public async Task<IActionResult> ExternalLogin([FromBody] string provider)
{
var redirectUrl = "/ExternalLoginCallback";
AuthenticationProperties properties = _repository.Account.ExternalLogin(provider, redirectUrl);
return Challenge(properties, provider);
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection(AppSettings.JWTSettings));
services.Configure<AppSettings>(Configuration.GetSection(AppSettings.EmailConfiguration));
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.ConfigureSqliteContext(Configuration);
services.ConfigureLoggerService();
services.ConfigureDependencyInjection(Configuration);
services.AddControllers();
services.AddAutoMapper();
services.AddIdentity<User, IdentityRole>(opt =>
{
opt.Password.RequiredLength = 0;
opt.Password.RequireDigit = false;
opt.Password.RequireUppercase = false;
opt.Password.RequiredUniqueChars = 0;
opt.Password.RequireNonAlphanumeric = false;
opt.User.RequireUniqueEmail = true;
opt.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<RepositoryContext>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddGoogle(GoogleDefaults.AuthenticationScheme, conf =>
{
var googleAuth = Configuration.GetSection("Google");
conf.ClientId = googleAuth["ClientId"];
conf.ClientSecret = googleAuth["ClientSecret"];
conf.SignInScheme = IdentityConstants.ExternalScheme;
});
if (Environment.IsDevelopment())
{
services.ConfigureAuthDevelopment(Configuration);
}
else
{
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
Edge Dev Console error:
Access to fetch at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=******-o1gn4lob5hcknjggl10837m3ea5om5b4.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Flocalhost%3A44313%2Fsignin-google&scope=openid%20profile%20email&state=CfDJ8Ok9DyPEKDNCp3gn7utujrjiLfNm4KnoB6RC-cf3g01gBFzNSAqo54K-GaMudVevgRKaJZbcT5_O9S-jCGixs0i5SPY_CUxvM-l_DDsckOP0iHQMgyncA_-Ce_8vgCuxeozNkRWOHDJWar174-TkOgulJpYTr7b82MtOkUs3FOmiqpJ42YU2Q74y9imEFsgk8lynBRzb8Qqvh7P5kMuIg85QNnpjeO6lFbvefLDXPIzzE2r0n5sYlo1vzUG2sRbtRNgfThR6TvF-LPuMGygVCRI' (redirected from 'https://localhost:44313/api/account/ExternalLogin?provider=Google') from origin 'https://localhost:44313' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
dotnet.5.0.2.js:1 GET https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=*******-o1gn4lob5hcknjggl10837m3ea5om5b4.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Flocalhost%3A44313%2Fsignin-google&scope=openid%20profile%20email&state=CfDJ8Ok9DyPEKDNCp3gn7utujrjiLfNm4KnoB6RC-cf3g01gBFzNSAqo54K-GaMudVevgRKaJZbcT5_O9S-jCGixs0i5SPY_CUxvM-l_DDsckOP0iHQMgyncA_-Ce_8vgCuxeozNkRWOHDJWar174-TkOgulJpYTr7b82MtOkUs3FOmiqpJ42YU2Q74y9imEFsgk8lynBRzb8Qqvh7P5kMuIg85QNnpjeO6lFbvefLDXPIzzE2r0n5sYlo1vzUG2sRbtRNgfThR6TvF-LPuMGygVCRI net::ERR_FAILED
[]
Edge Dev Console network header
Request URL: https://localhost:44313/api/account/ExternalLogin?provider=Google
Request Method: GET
Status Code: 302
Remote Address: [::1]:44313
Referrer Policy: strict-origin-when-cross-origin
My assumption is that Challenge() produces a redirect which is then "blocked by CORS policy" from Blazor.
What would be the simplest yet more efficient way to implement it?

Related

ASP .NET Core CORS issue with Google authentication on redirect

Been following this tutorial in order to implement Google authentication in my web API but on the client side (using React and axios to do the request) the authentication process gets interrupted with this CORS issue and I'm struggling to sort it out:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?(etc)' (redirected from 'https://localhost:44320/Photo/b997d788-3812-41d0-a09d-1a597eee9bad') from origin 'https://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is the Startup.cs file:
namespace rvc
{
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.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
});
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.LoginPath = "/account/google-login";
}).AddGoogle(options =>
{
options.ClientId = "clientId";
options.ClientSecret = "secret";
});
services.AddScoped<PhotoService>();
services.AddScoped<TagService>();
services.AddScoped(_ => new BlobServiceClient(Configuration.GetConnectionString("AzureBlobStorage")));
services.AddDbContext<Data.DataContext>(x => x.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "rvc", Version = "v1" }); });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "rvc v1"));
}
app.UseHttpsRedirection();
if (env.IsProduction())
{
app.UseSpa(spa => { });
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.ContentRootPath, "client")),
EnableDefaultFiles = true
});
}
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
}
The Route("google-login") gets called but the Url.Action("GoogleResponse") is not reached. These are the Google Authentication methods:
namespace rvc.Controllers;
[AllowAnonymous, Route("account")]
public class AccountController : Controller
{
[Route("google-login")]
public IActionResult GoogleLogin()
{
var properties = new AuthenticationProperties {RedirectUri = Url.Action("GoogleResponse")};
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
[Route("google-response")]
public async Task<IActionResult> GoogleResponse()
{
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var claims = result.Principal?.Identities.FirstOrDefault()
?.Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
return Json(claims);
}
}
This is probably because from the server you use redirect, which triggers CORS (even if from your server you allow it).
you have to return the redirect URL to your front-end in some other way, capture it from the front-end app and then call the URL you need to invoke.

Enabling CORS in ASP.NET Core 6

I have an ASP.NET Core 6 Web API that has a react front end.
I would like to use named policies to enable cors so I have in my startup
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("MyPolicy",
builder => builder.WithOrigins("http://localhost:3000/"));
});
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// Shows UseCors with named policy.
app.UseCors("MyPolicy");
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Now I can call it in the controller like this:
[EnableCors("MyPolicy")]
public class ProductsController : ControllerBase
This worked in .NET Core 3.1 but not in .NET 6.
What is the right way of doing this in an ASP.NET Core 6 Web API?
Changing the program CS to acomodate CORS policy still doesnt work
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StoreContext>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
try
{
context.Database.Migrate();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
logger.LogError(ex, "Problem migrating data");
}
var builder = WebApplication.CreateBuilder(args);
var MyAllowSpecificOrigins = "AnotherPolicy";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<StoreContext>(options =>
{
options.UseSqlite(connectionString);
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
try
{
app.Run();
}
catch (Exception)
{
throw;
}
//host.Run();
}
This gives me the same error
Access to fetch at 'http://localhost:5000/api/Products' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
in program.cs file:
app.UseCors(
options => options.WithOrigins("*").AllowAnyMethod().AllowAnyHeader()
);
As noted in the documentation:
The specified URL must not contain a trailing slash (/). If the URL terminates with /, the comparison returns false and no header is returned.

Is it possible to use tokens for API and cookies for get Razor page or Controller Action in the same app with using identity server?

I have an SPA project with .net core 3.1 & identity server and react.i want to Authorize API with token and Authorize Controller Action or Razor Pages with cookies in Get Request, Is it possible to use both of them in same app?
For example, I want admin area to be Authorize with cookie and the admin to have access to its views by cookie but User area works with react and Api by token.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
var builder = services.AddIdentityServer()
.AddApiAuthorization<IdentityUser, ApplicationDbContext>();
builder.Services.ConfigureExternalCookie(options => {
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode.Unspecified);
});
builder.Services.ConfigureApplicationCookie(options => {
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode.Unspecified);
});
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapControllerRoute("adminArea", "{area=Admin}/{controller=Dashboard}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
Yes, it's possible to authorize with different auth schemes in an ASP.Net Core application, you just have to specify it on the [Authorize] tag that you call before the method.
First you have to add both authentications at the services.AddAuthentication() call.
Then, lets say that you want your method to authenticate with JWT token:
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
On the other hand, if you want it to authenticate with a cookie:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
You can check more of how it works at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-3.1.

.netcore web API Post does not work without SSL

I have an MVC core 2.2 application with Controllers, API controllers, and some Views. Some of the Views make use of the API endpoints within the same application. All was ok until I enabled TLS 1.2 in my windows server.
Now all endpoints decorated as GET methods work. But all POSTs have stopped working with chrome reporting this POST 'link' net::ERR_CONNECTION_RESET.
Other browsers catch exception at fail and display my error text with object
$.ajax({
url: "/api/Cart",
method: "post",
data: JSON.stringify(vm),
contentType: "application/json"
}).done(function(result) {
console.log(result);
}).fail(function(ex) {
console.log("Error occured while adding to cart" + ex)
});
I want to be able to POST even without SSL. I have disabled TLS 1.2 and restarted but still the same result.
If I browse the site with https://, POST and GET endpoints all work perfectly but if I browse with HTTP://, only GET endpoints work, all POST endpoints do not work.
I have combed the internet for almost 5 hours for something I thought would be simple.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DataConnection");
services.AddDbContext<DataContext>(options => options.UseSqlServer(connectionString));
//Inject Connection String to other Classes
services.AddSingleton(_ => connectionString);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
});
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddMediatR(typeof(CreateProductCommand).Assembly, typeof(CreateProductCommandHandler).Assembly);
services.AddAutoMapper(typeof(MappingProfile));
// Add memory cache services
services.AddMemoryCache();
services.AddMvc(o =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
o.Filters.Add(new AuthorizeFilter(policy));
}) .SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2);
}
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var serviceProvider = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider;
DataContextSeed.Initialize(serviceProvider);
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Above is the Startup class and a sample post is here
[HttpPost]
public IActionResult Post([FromBody] SaleItemDTO md)
{
if(md != null)
{
if(md.Quantity <= md.Stock)
{
_sales.SalesPerson = User.Identity.Name;
_sales.SalesType = md.SalesType;
return Ok(_sales.ItemsInDb);
}
}
return BadRequest(new { Message = "Not Valid Content posted" });
}
Help with your thoughts.

ASP Core 3 react template, HttpContext.User.IsAuthenticated() returns False after login

After working on my project for a while, I released the HttpContext.User.IsAuthenticated() returns False after login and I need to know where I should look for the mistake I made that cause this problem.
This is the Login, OnPost method.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = _userManager.Users.FirstOrDefault(u => u.StudentNumber == Input.StudentNumber.ToString());
if (!(user is null) && await _userManager.CheckPasswordAsync(user, Input.Password))
await _signInManager.SignInAsync(user, Input.RememberMe);
var isUserAuthenticated = HttpContext.User.IsAuthenticated();
return Redirect(returnUrl);
}
// If we got this far, something failed, redisplay form
return Page();
}
The ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(option=>option.Password.RequireNonAlphanumeric=false)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddMvc(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
The Configure method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
SignInManager.SignInAsync() only creates the cookie for the given user. This method would not set HttpContext.User.
But in the next request which has the cookie you can access HttpContext.User after AuthenticationMiddleware and HttpContext.User.IsAuthenticated() should be true.
AuthenticationMiddleware always try to authenticate user with the default scheme and since you have AddIdentityServer after AddDefaultIdentity, identity server is becoming your default scheme, but when you call SignInManager.SignInAsync the Identity scheme is triggered.
To sum up, with this configuration your AuthenticationMiddleware always tries to authenticate request for IdentityServer and if you want other scheme for you apis you should use [Authorize(AuthenticationSchemes = "Identity.Application")].
P.S. Identity.Application is authenticatio scheme for ASP.NET Identity