Asp.Net MVC 6 Identity 3 MongoDB External Login (Facebook) - asp.net-mvc

I'm running the RC1 of ASP.NET MVC 6 and would like to use a MongoDB Identity Provider.
I have implemented the provider by Grant Megrabyan which is doing a great job of registering new users and allowing them to log in but I get the error:
InvalidOperationException: No authentication handler is configured to handle the scheme: Microsoft.AspNet.Identity.External
Microsoft.AspNet.Http.Authentication.Internal.DefaultAuthenticationManager.d__13.MoveNext()
I had the external login previously working using EntityFramework so I'm assuming my configuration for third party auth is probably correct.
When the user clicks login with Facebook they are redirected to the following action :
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
There is no exception thrown at this point however when the Facebook returns from the ChallengeResponse it sends a GET to : http://localhost:51265/signin-facebook?code=[facebook user token].
At this point ASP.NET throws the exception:
The callback url made by Facebook doesn't seem to make sense. Surely it should return to my ExternalLoginCallback action?
It's about here that I'm out of ideas?!
If anyone can see where I've gone wrong then I'd be a very happy guy.
My startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApplicationDbContext>();
// Add framework services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddMongoStores<ApplicationDbContext, ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});
app.UseIdentity();
app.UseMvcWithDefaultRoute();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}

Call UseFacebookAuthentication after UseIdentity, but before UseMvc;
app.UseIdentity();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});
app.UseMvcWithDefaultRoute();

Edit:
I think the problem is the Order of defining configurations. Try to add Identity first then Facebook Authentication.
** Suggested Example **
app.UseIdentity();
app.UseMvcWithDefaultRoute();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});

Related

Asp.Net Core Twitch OAuth: Correlation Failed

I configured Twitch authentication for my website using this tutorial: https://blog.elmah.io/cookie-authentication-with-social-providers-in-asp-net-core/
In the Twitch dev console I added the https://localhost:44348/signin-twitch url to the callback urls. I've implemented oauth for other providers before, but having troubles with Twitch.
When I try to login, I get the Twitch authorization screen, but after clicking 'Authorize' it redirects me to /signin-twitch + params which returns a Correlation Failed exception.
Exception: An error was encountered while handling the remote login.
I have a feeling it might have to do with the routing. It's setup like this because I have a frontend application with it's own routing (hence the Fallback)
Here is all relevant code.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
})
.AddTwitch(TwitchAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ClientId = "xxx";
options.ClientSecret = "xxx";
options.Scope.Add("user:read:email");
options.SaveTokens = true;
options.AccessDeniedPath = "/";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapFallbackToController("Index", "Home");
});
}
public class AuthenticationController : Controller
{
[HttpGet("~/signin")]
public IActionResult SignIn(string returnUrl = "")
{
return Challenge(TwitchAuthenticationDefaults.AuthenticationScheme);
}
}
I think that the error occurs because you're trying to access the URL which is assigned as Callback Path.
Try some variant of this:
[HttpGet("~/signin")]
public IActionResult SignIn()
{
var authProperties = _signInManager
.ConfigureExternalAuthenticationProperties("Twitch",
Url.Action("LoggingIn", "Account", null, Request.Scheme));
return Challenge(authProperties, "Twitch");
}
Source: this answer and this one.
Other stuff to check:
Multiple clients with the same Callback Path
CookiePolicyOptions
HTTPS redirect

Pass TempData between Contollers for Modal return Null?

I want to pass Some String between two controllers to show successful login with modal.I read this topics :
ViewBag, ViewData and TempData
and
RedirectToAction with parameter
but it doesn't work for me and TempData returns Null.it's works fine in this Controllers.
public async Task<IActionResult> LoginConfirm(LoginViewModel model)
{
ApplicationUser user = await userManager.FindByNameAsync(model.Email);
if (user!=null)
{
var status = await signInManager.PasswordSignInAsync(user, model.Pass,model.RememberMe,true);
if (status.Succeeded)
{
TempData["msg"] = "You Login successful ";
return RedirectToAction("Index","Home");
}
}
TempData["msg"] = "Somethings Wrong!";
return View("Login");
}
you have two way
1)
when you using the
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
you enable the GDPR ( General Data Protection Regulation ) And so for as long as the user does not accept your cookie, you will not be able to set cookie in site. And that makes the TempData empty.
2)
After Migrating to ASP Core 2.1 I had this issue and after working for a day find the solution:
in Startup.Configure() app.UseCookiePolicy(); should be after app.UseMVC();
namespace GiftSite
{
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// 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");
// 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.UseHttpMethodOverride();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

Cannot get user identity set in Web Core API v1

I have a Web Core API version 1 project, where when I call a method via Postman I find the [Authorize] tag does not work.
In my Web API my startup looks like this (edited for readability)
public void ConfigureServices(IServiceCollection services)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(typeof(Startup).Assembly));
services.AddSingleton(manager);
services.AddCors();
services.AddMvcCore().AddJsonFormatters();
services.Configure<IISOptions>(options => new IISOptions
{
AutomaticAuthentication = true,
ForwardClientCertificate = false,
ForwardWindowsAuthentication = false
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
RequireHttpsMetadata = false,
Authority = Settings.AuthorityUrl,
ApiName = Settings.ApiName
});
app.UseStaticFiles();
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();
app.UseMvc();
}
In my OAuth server I am using the Quickstart for IdentityServer4
public class Startup
{
// 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.AddIdentityServer()
.AddSigningCredential(new X509Certificate2(Settings.CertPath, Settings.Password))
.AddTestUsers(InMemoryConfiguration.Users().ToList())
.AddInMemoryClients(InMemoryConfiguration.Clients())
.AddInMemoryApiResources(InMemoryConfiguration.ApiResources());
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = Settings.AuthorityUrl;
options.ApiName = Settings.ApiName;
});
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, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
And this is the method I am calling in Postman;
[HttpGet("get")]
public async Task<IActionResult> Get()
{
var claims = User.Claims;
var username = User.Identity.Name;
this.NLogger.Info("api/comboboxdata/get".ToPrefix());
try
{
var container = new ComboBoxData(this.SirUoW);
return Ok(container);
}
catch (Exception e)
{
var message = "Error getting combo box data";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}
In Postman I get the bearer token and put it in the header. The method is successfully called and returns the data. The claims are also set as expected, and when the token expires the claims are empty. However [Authorize] does not block the request if the token is invalid or is not sent.
The Authorize attribute is at the beginning of the controller;
[Authorize]
[Route("api/comboboxdata")]
public class ComboBoxDataController : BaseSirController
{
How do I put this right?
You should add AddAuthorizationmethod to enable authorization services in your web api :
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();

Why does SSO between ASP.NET MVC and ASP.NET Core 2.0 only work on localhost?

I have a problem with SSO between my two web apps.
I am using code from this tutorial https://learn.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-2.1&tabs=aspnetcore2x
First app based on ASP .NET MVC:
public partial class Startup
{
public CronJobs _cronJobs;
public Startup() { }
public Startup(CronJobs cronJobs)
{
_cronJobs = cronJobs;
}
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationUserDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
AuthenticationType = "Identity.Application",
CookieName = ".AspNet.SharedCookie",
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) =>
user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new AspNetTicketDataFormat(
new DataProtectorShim(
DataProtectionProvider.Create(new DirectoryInfo(#"c:\keyring"),
(builder) => { builder.SetApplicationName("SharedCookieApp"); })
.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Identity.Application",
"v2"))),
CookieManager = new ChunkingCookieManager()
});
System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var hangfireContainer = new UnityContainer();
GlobalConfiguration.Configuration.UseActivator(new UnityJobActivator(hangfireContainer));
GlobalConfiguration.Configuration.UseSqlServerStorage("HangFireDB");
app.UseHangfireServer();
//this call placement is important
var options = new DashboardOptions
{
Authorization = new[] { new CustomAuthorizationFilter() }
};
app.UseHangfireDashboard("/hangfire", options);
}
}
public class CustomAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
if (HttpContext.Current.User.IsInRole("admin"))
{
return true;
}
return false;
}
}
And my second app (Core 2.0)
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.AddDbContext<CatalogDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CatalogConnection")));
services.AddDbContext<UsersDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("UsersConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<UsersDbContext>()
.AddDefaultTokenProviders();
services.AddDataProtection()
.PersistKeysToFileSystem(GetKeyRingDirInfo())
.SetApplicationName("SharedCookieApp");
services.ConfigureApplicationCookie(options => {
options.Cookie.Name = ".AspNet.SharedCookie";
});
services.AddTransient<UserManagerInfo>();
services.AddMvc();
}
private DirectoryInfo GetKeyRingDirInfo()
{
var keyRingDirectoryInfo = new DirectoryInfo("C://keyring");
if (keyRingDirectoryInfo.Exists)
{
return keyRingDirectoryInfo;
}
throw new Exception($"KeyRing folder could not be located");
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var options = new RewriteOptions().AddRedirectToHttpsPermanent();
app.UseRewriter(options);
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseStatusCodePages();
app.UseStatusCodePagesWithRedirects("/error/{0}");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
It's working on localhost. It is not working on IIS Windows Server 2016. No errors, but it not works.
Both apps have a permission to read and write to folder "keyring".
Please check the following and let me know if anything helped, I am ready to advise you further.
Check if you have set the right SameSite cookie policy - based on the way you deploy the website to IIS. https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-2.0
Check if you do correctly SingIn the user in you ASP.NET CORE 2.0 application. (Calling SingInAsync in your code. https://dotnetcoretutorials.com/2017/09/16/cookie-authentication-asp-net-core-2-0/)
This may be caused by thousands of things. If nothing from the list helps, please provide further info on the configuration of IIS. - The way you deploy both apps.

How do i get this Basic Authentication working in .NET Core

I am using Basic Authentication and I have created a Middleware for this. When I use the Authorize attribute and try to make an API call through Postman, i get 401 Unauthorize even as I am added the authorization details in Postman.
I am not sure if it is the way i am calling the controller action via postman or whether I am missing a header option in postman.
ConfigureServices
services
.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
.AddBasicAuthentication(GetBasicAuthenticationOptions(users));
Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseCors("SiteCorsPolicy");
app.UseAuthentication();
}
Basic Authentication Implementation
private Action<BasicAuthenticationOptions> GetBasicAuthenticationOptions(IList<UserConfiguration> users)
{
return options =>
{
options.Realm = "Public API";
options.Events = new BasicAuthenticationEvents
{
OnValidatePrincipal = context =>
{
var authenticatedUser = users.FirstOrDefault(u =>
u.Username == context.UserName && u.Password == context.Password);
if (authenticatedUser != null)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,
context.UserName,
context.Options.ClaimsIssuer)
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims,
BasicAuthenticationDefaults.AuthenticationScheme));
context.Principal = principal;
return Task.CompletedTask;
}
return Task.FromResult(AuthenticateResult.Fail("Authentication failed."));
}
};
};
}
Controller Action with Authorize
[Authorize]
[HttpPost]
[Route("test")]
public IActionResult Test()
{
return Json("yes");
}
Postman request
The username and passwords are in the appsettings.json file
The issue was just because app.UseMvc(); was placed before app.UseAuthentication(); . I just had to place app.UseMvc(); after app.UseAuthentication(); and that fixed it.

Resources