问题
While implementing external social login in ASP.NET Core2.2 MVC web application without ASP.NET Core Identity. I am getting below error while redirecting back to the application after successful sign in with Google, Facebook, Twitter, LinkedIn and Microsoft.
An unhandled exception occurred while processing the request. Exception: Invalid state cookie. Unknown location
Exception: An error was encountered while handling the remote login. Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
Following are the settings in Startup.cs file
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(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.IsEssential = true;
})
.AddGoogle(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.CallbackPath = "/externallogincallback";
})
.AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
facebookOptions.CallbackPath = "/externallogincallback";
})
.AddLinkedIn(linkedinOptions =>
{
linkedinOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
linkedinOptions.ClientId = Configuration["Authentication:LinkedIn:ClientId"];
linkedinOptions.ClientSecret = Configuration["Authentication:LinkedIn:ClientSecret"];
linkedinOptions.CallbackPath = "/externallogincallback";
})
.AddTwitter(twitterOptions =>
{
twitterOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
twitterOptions.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
twitterOptions.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
twitterOptions.CallbackPath = "/Home/externallogincallback";
}).AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ClientId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
microsoftOptions.CallbackPath = "/externallogincallback";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Following are the details of HomeController.cs (As I am not using Identity so I need to define redirect url specifically.)
//Action to issue a challange to google login
public IActionResult LogInMicrosoft(string provider)
{
//provider = Microsot or Google or LinkedIn or Twitter or Facebook
provider = "Microsoft";
//Issue a challenge to external login middleware to trigger sign in process
//return new ChallengeResult(provider);
var authenticationProperties = new AuthenticationProperties
{
RedirectUri = Url.Action("externallogincallback")
};
return Challenge(authenticationProperties, provider);
//return new ChallengeResult(provider);
}
//Callback action to retrive signin user details
//public IActionResult externallogincallback(string returnUrl = null, string remoteError = null)\
public IActionResult externallogincallback()
{
//Here we can retrieve the claims
// read external identity from the temporary cookie
//var authenticateResult = HttpContext.GetOwinContext().Authentication.AuthenticateAsync("ExternalCookie");
var result = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (result.Result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var externalUser = result.Result.Principal;
if (externalUser == null)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var claims = externalUser.Claims.ToList();
// try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
//var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
var userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
if (userIdClaim == null)
{
throw new Exception("Unknown userid");
}
var externalUserId = userIdClaim.Value;
var externalProvider = userIdClaim.Issuer;
// use externalProvider and externalUserId to find your user, or provision a new user
return RedirectToAction("Privacy", "Home");
}
回答1:
It seems you want to redirect the request to externallogincallback
after sigin the microsoft account. If so, you should not set microsoftOptions.CallbackPath
with externallogincallback
. With this setting, all the request from Microsoft will be handled by OAuth Middleware instead of your own endpoint externallogincallback
.
For redirect page after sign, you need to pass return Challenge(authenticationProperties, provider);
by setting the authenticationProperties.authenticationProperties
Follow steps below:
- Change the
REDIRECT URI
in Azure portal withhttps://localhost:xxx/signin-microsoft
Change
Startup.cs
withpublic 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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; //options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Cookie.IsEssential = true; //options.Cookie.SameSite = SameSiteMode.None; }) .AddMicrosoftAccount(microsoftOptions => { microsoftOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ClientId"]; microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"]; }); } // 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.UseStaticFiles(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }
HomeController
public class HomeController : Controller { //Action to issue a challange to google login public IActionResult LogInMicrosoft(string provider) { //provider = Microsot or Google or LinkedIn or Twitter or Facebook provider = "Microsoft"; var authenticationProperties = new AuthenticationProperties { RedirectUri = Url.Action("externallogincallback") }; return Challenge(authenticationProperties, provider); } [Route("/[action]")] public async Task<IActionResult> externallogincallback() { var request = HttpContext.Request; //Here we can retrieve the claims // read external identity from the temporary cookie //var authenticateResult = HttpContext.GetOwinContext().Authentication.AuthenticateAsync("ExternalCookie"); var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); if (result.Succeeded != true) { throw new Exception("External authentication error"); } // retrieve claims of the external user var externalUser = result.Principal; if (externalUser == null) { throw new Exception("External authentication error"); } // retrieve claims of the external user var claims = externalUser.Claims.ToList(); // try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier // depending on the external provider, some other claim type might be used //var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject); var userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier); if (userIdClaim == null) { throw new Exception("Unknown userid"); } var externalUserId = userIdClaim.Value; var externalProvider = userIdClaim.Issuer; // use externalProvider and externalUserId to find your user, or provision a new user return RedirectToAction("Privacy", "Home"); } public IActionResult Index() { return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
来源:https://stackoverflow.com/questions/57240285/invalid-state-cookie-an-error-was-encountered-while-handling-the-remote-login