Setting Up Social Authentication in ASP.NET Core 2.0 - asp.net-core

I'm setting up social login in an ASP.NET Core 2.0 application without using Identity.
I simply want to authenticate the user through Facebook, Google and LinkedIn and receive their info. I handle storing user info myself.
Here's what I've done so far which is giving me the following error:
No authentication handler is configured to handle the scheme: facebook
Here's the Startup.cs file changes:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Added these lines for cookie and Facebook authentication
services.AddAuthentication("MyCookieAuthenticationScheme")
.AddCookie(options => {
options.AccessDeniedPath = "/Account/Forbidden/";
options.LoginPath = "/Account/Login/";
})
.AddFacebook(facebookOptions =>
{
facebookOptions.AppId = "1234567890";
facebookOptions.AppSecret = "1234567890";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// Added this line
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I then have this action method where I send the user to determine the provider we're using for authenticaiton e.g. Facebook, Google, etc. This code came from my ASP.NET Core 1.1 app which is working fine.
[AllowAnonymous]
public async Task ExternalLogin(string provider, string returnUrl)
{
var properties = new AuthenticationProperties
{
RedirectUri = "Login/Callback"
};
// Add returnUrl to properties -- if applicable
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
properties.Items.Add("returnUrl", returnUrl);
// The ASP.NET Core 1.1 version of this line was
// await HttpContext.Authentication.ChallengeAsync(provider, properties);
await HttpContext.ChallengeAsync(provider, properties);
return;
}
I'm getting the error message when I hit the ChallangeAsync line.
What am I doing wrong?

No authentication handler is configured to handle the scheme: facebook
Scheme names are case-sensitive. Use provider=Facebook instead of provider=facebook and it should work.

Related

ASP.NET Core 5: OpenIDConnect breaking default/root route

I have an ASP.NET Core 5 MVC app, with the default/root route set like this inside PageController:
[AllowAnonymous]
[Route("/")]
public IActionResult __Home(int? parent)
{
return View();
}
This worked fine until I added OpenIdConnect authentication. After that, the root (/) page no longer routes to __Home in the PageController, it just returns a blank page. All other pages route just fine.
When I comment out this:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
then / works again, so I know it's something to do with the authentication. As you can see, I have added [AllowAnonymous] to that action.
I have this in my startup:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
});
Any ideas on how to fix this? I know it's unconventional to have the default/root route in a weird controller/action like that, but there are reasons for it, so I'm hoping it can still work.
More Info:
I found that if I move app.UseEndpoints above app.UseAuthentication, then the home page shows. After logging in (with B2C), however, it goes into an infinite loop (i.e. the authentication token doesn't stick?).
EDIT: My Startup.cs class
using Blank.Models;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
namespace Blank
{
public class Startup
{
private readonly AppSettings appSettings = null;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
this.appSettings = new AppSettings();
this.Configuration.Bind(this.appSettings);
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
services.AddSession();
services.Configure<OpenIdConnectOptions>(Configuration.GetSection("AzureAdB2C"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<AppSettings>(this.Configuration);
services.AddEntityFrameworkSqlServer().AddDbContext<BlankDBContext>(
Options => Options.UseSqlServer(Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString(this.Configuration, "BlankDatabase"))
);
}
// 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();
}
else
{
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.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Page}/{action=Index}/{id?}");
});
}
}
}
Edit 2
I think that app.UseAuthentication() is breaking/returning the blank page, because when I put the following code before app.UseAuthentication() I get something on the home page, and if it's after then blank:
app.Use(async (context, next) =>
{
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
await context.Response.WriteAsync("<html> Endpoint :" + endpoint.DisplayName + " <br>");
if (endpoint is RouteEndpoint routeEndpoint)
{
await context.Response.WriteAsync("RoutePattern :" + routeEndpoint.RoutePattern.RawText + " <br>");
}
}
else
{
await context.Response.WriteAsync("End point is null");
}
await context.Response.WriteAsync("</html>");
await next();
});
So perhaps it has to do with my authentication? Here's my appsettings.json:
"AzureAdB2C": {
"Instance": "https://abc.b2clogin.com",
"Domain": "abc.onmicrosoft.com",
"ClientId": "62...f1",
"TenantId": "7e...ae",
"SignUpSignInPolicyId": "B2C_1_SUSI",
"SignedOutCallbackPath": "/"
},
Turns out the problem was this in my appsettings.json:
"SignedOutCallbackPath": "/"
Removing this fixed the problem, and the home page now loads correctly.

.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.

IdentityServer - How to bypass authorization for simple debug

I have several .NET core API and I use IdentityServer 4 as a seperate service for authentication.
The problem is that in "debug" I also wish to run my API without authentication (without launching the IdentityServer).
So, I try to bypass it... I have try several solutions, but none work:
- With a AuthorizationHandler: Bypass Authorize Attribute in .Net Core for Release Version
- With a Middleware : Simple token based authentication/authorization in asp.net core for Mongodb datastore
- With a filter : ASP.NET Core with optional authentication/authorization
- With AllowAnonymousFilter : Bypass Authorize Attribute in .Net Core for Release Version
But no way, none of theses solutions work, I still got a "401 Undocumented Error: Unauthorized" !
Here is some parts of my code:
public void ConfigureServices(IServiceCollection services)
{
// JSON - setup serialization
services.AddControllers().
AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(new TargetSpot.Core.Json.SnakeCaseNamingStrategy()));
options.JsonSerializerOptions.IgnoreNullValues = true;
});
// Force lowercase naming
services.AddRouting(options => options.LowercaseUrls = true);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Setup the connection to the IdentityServer to request a token to access our API
services.AddAuthentication(IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetSection("APISettings")["AuthorityURL"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration.GetSection("APISettings")["APIName"];
});
// Add swagger
services.AddSwaggerGen(options =>
{
//options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "HTTP API",
Version = "v1",
Description = "The Service HTTP API",
TermsOfService = new Uri("http://www.myurl.com/tos")
});
// XML Documentation
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// 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.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger().UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Winamp API v1");
});
}
I had similar problem.
AllowAnonymousFilter works in ASP.NET Core 2.2 but not in ASP.NET Core 3.x.
After day of investigation I have found out that switching from UseEndpoints to UseMvc solved it and I can now disable authentication without commenting out [Authorize] attributes.
It seems that UseEndpoints does not use filter when registered by AddMvc but how to correctly register it when using UseEndpoints I do not know.
My solution
Startup.ConfigureServices:
services.AddMvc(o =>
{
o.EnableEndpointRouting = false;
o.Filters.Add(new AllowAnonymousFilter());
});
Startup.Configure:
// anonymous filter works with UseMvc but not with UseEndpoints
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
//});
I found the solution in this link: https://docs.identityserver.io/_/downloads/en/latest/pdf/. Obviously I had to remove the Authorize attributes I added manually in my controllers.
app.UseEndpoints(endpoints =>
{
// Allowing Anonymous access to all controllers but only in Local environment
if (env.IsEnvironment(Constants.ApplicationConstants.LocalEnvironment))
endpoints.MapControllers();
else
endpoints.MapControllers().RequireAuthorization();
});

ASP.net-core 3.0 - Is it possible to return custom error page when user is not in a policy?

I'm creating an intranet website and I'm having some trouble with the authentication part. I would like to limit the access for a controller to users in a specific Active Directory Roles. If the user is not in the specified Roles, then it should redirect him to a custom error page.
Windows authentication is enabled. I've tried the following solutions :
I created a custom policy in my ConfigureServices method inside my Startup.cs :
...
services.AddAuthorization(options =>
{
options.AddPolicy("ADRoleOnly", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole(Configuration["SecuritySettings:ADGroup"], Configuration["SecuritySettings:AdminGroup"]);
});
});
services.AddAuthentication(IISDefaults.AuthenticationScheme);
....
with inside my appsettings.json my active directory groups (not the one i'm really using of course) :
"SecuritySettings": {
"ADGroup": "MyDomain\\MyADGroup",
"AdminGroup": "MyDomain\\MyAdminGroup"
}}
and inside my Configure method :
...
app.UseAuthorization();
app.UseAuthentication();
app.UseStatusCodePagesWithReExecute("/Home/ErrorCode/{0}");
...
I have the following controller :
[Area("CRUD")]
[Authorize(Policy = "ADRoleOnly")]
public class MyController : Controller
I have a HomeController with the following method :
[AllowAnonymous]
public IActionResult ErrorCode(string id)
{
return View();
}
but when I debug my site, this method is never reached.
If I'm a user inside one of the specified roles of my policy, it's all working as expected.
But if I'm not a member of the roles, I'm redirected to the default navigator page.
I would like to redirect to a custom error page. I thought that was the purpose of
app.UseStatusCodePagesWithReExecute("/Home/ErrorCode/{0}");
It will generate a 403 statuscode when the policy fails,app.UseStatusCodePagesWithReExecute does not detect 403:
UseStatusCodePagesWithReExecute is not working for forbidden (403)
You could write a custom middleware to deal with it :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
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.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 403)
{
var newPath = new PathString("/Home/ErrorCode/403");
var originalPath = context.Request.Path;
var originalQueryString = context.Request.QueryString;
context.Features.Set<IStatusCodeReExecuteFeature>(new StatusCodeReExecuteFeature()
{
OriginalPathBase = context.Request.PathBase.Value,
OriginalPath = originalPath.Value,
OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null,
});
// An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
// the endpoint and route values to ensure things are re-calculated.
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
routeValuesFeature?.RouteValues?.Clear();
context.Request.Path = newPath;
try
{
await next();
}
finally
{
context.Request.QueryString = originalQueryString;
context.Request.Path = originalPath;
context.Features.Set<IStatusCodeReExecuteFeature>(null);
}
// which policy failed? need to inform consumer which requirement was not met
//await next();
}
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}

Run a custom method immediately after Azure AD Authentication

I have an ASP.NET Core Web App which successfully uses Azure AD Authentication. I would like to run a process immediately after a user logs in. I thought I might somehow handle the Redirect URI specified in the Azure app registration but I couldn't figure it out as much of the login process is nicely handled by the .AddAzureAd() method in my Startup.cs.
Can anyone suggest an easy way to call a method or redirect to a razor page after authentication? Preferably something which would not be circumvented by specifying a returnUrl in the initial request.
Update
Between posting the question and seeing the answers I found what might be considered a hack:
Basically I created a service and injected it into my _LoginPartial.cshtml page and then call a method on the service.
...
#inject MyService myService
...
#if (User.Identity.IsAuthenticated)
{
await MyService.MyCustomMethod();
...
}
For running code or changing the redirect url, you could configure OpenIdConnectOptions.
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = ctx =>
{
ctx.Properties.RedirectUri = "/Home/Privacy";
return Task.CompletedTask;
},
};
});
If you want to run code after authentication, you could place your code in the OnTokenValidated.
If you want to change the uri, you could replace /Home/Privacy.
You can define the route in Startup.cs file. I used the sample here.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Contact}/{id?}");
});
}