I am trying to get the cookie after a successful login but I can't figure out how.
On the ASP Net Core Identity API using swagger the browser gets the cookie but when I use the fetch API I cant get the cookie. I tried returning the return response.json();
but this does not work. I also have the redirecting to home page on login Success but I am not sure exactly how to return the return response.json();
if that is needed.
Both the Identity API and the JS - Client are running on localhost.
JS - Fetch API - POST:
function IdentityPost(formID, postUrl) {
const currForm = document.getElementById(formID); // Get the Form
var submitBtn = currForm.elements.namedItem("triggerSubmit"); // Get the submit button of the form
// Listen for Form- Submit
currForm.addEventListener('submit',function handler(e)
{
e.preventDefault(); // Prevent page reload on Submit
submitBtn.disabled = true; // Disable the submit button
LoadingMsg(); // Show Loading Message
// Get form data as string---------------------------------------------------------------
const formData = new FormData(this); // "this" = this Form
const searchParams = new URLSearchParams(formData); // Get the form data params
let formQueryString = searchParams.toString(); // Get the form data params as string
// POST ----------------------------------------------------------------------------------
fetch(identityApiUri + postUrl + formQueryString, // #1 = API-Address, #2 = API - Controller/Mehod, #3 = form data as sring
{
method: 'POST',
credentials: 'same-origin'
}).then(function (response)
{
// IF OK
if (response.status == 200 || response.status == 201) // Status 201 = "Created"
{
RemoveLoadingMsg();
SuccessMsg("Success");
currForm.reset(); // Reset the form
submitBtn.disabled = false; // Enable Submit button
if (document.referrer.split('/')[2] === window.location.host) // Return to previous page if local
{
history.back(); // Go back to previouse page
}
else
{
window.location.href = "/"; // RETURN TO Home
}
}
else // If Bad STATUS
{
return Promise.reject(response); // Triggers Catch method
}
}).catch(function (err) // If Exception
{
RemoveLoadingMsg();
// Show Error
try // Because of JSON Parse and err.text()
{
err.text().then(errorMessage => {
var error = errorMessage.substring(1, errorMessage.length - 1); // Remove the [..] form the Msg
ErrorMsg(error); // Get the error and display
});
}
catch(e)
{
console.warn("Post Exception - Probably No connection to hte server");
ErrorMsg(err + " - Server is probably offline"); // Get the error and display
}
submitBtn.disabled = false; // Enable Submit button
console.warn('Post Exception:', err);
});
this.removeEventListener('submit', handler); // Remove Event Listener
});
}
ASP Net Core - Identity API - Startup:
I have enabled CORS Any Origin. - Not sure if I need to include the .AllowCredentials()
. If I try to enable it it says that I can't have .AllowAnyOrigin()
enabled. I am accessing the API directly from the Client "Browser".
using Leanheat.Identity.API.DBContexts;
using Leanheat.Identity.API.Filters;
using Leanheat.Identity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Leanheat.Identity.API
{
public class Startup
{
// Startup
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// Configure Services =================================================================================
public void ConfigureServices(IServiceCollection services)// This method gets called by the runtime. Use this method to add services to the container.
{
// Log in - DbContext
services.AddDbContextPool<LeanheatIdentityApiContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IdentityContextConnection")));
// UnitOfWork - Filter
services.AddScoped<UnitOfWorkFilter>();
services.AddControllers(config => { config.Filters.AddService<UnitOfWorkFilter>(); }); // UnitOfWork for all Controllers
// CORS - Allow calling the API from WebBrowsers
services.AddCors();
// Log In
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
}).AddEntityFrameworkStores<LeanheatIdentityApiContext>().AddDefaultTokenProviders(); // AddDefaultTokenProviders is used for the Update Log In Password etc.
// Log In
// Make all Controllers protected by default so only Authorized Users can accsess them, for Anonymouse Users use [AlloAnonymouse] over the controllers.
services.AddMvc(options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddXmlSerializerFormatters();
//services.AddControllers();
// Swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Leanheat.Identity.API", Version = "v1" });
});
}
// Configure ===========================================================================================
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
{
// Default Code------------------------------------------------------------------------------------>
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Swagger
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Leanheat.Identity.API v1"));
}
app.UseHsts(); // Allow HTTPS
app.UseHttpsRedirection();
app.UseRouting();
// CORS - Allow calling the API from WebBrowsers
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.SetIsOriginAllowed(origin => true));// allow any origin
// Log In
app.UseAuthentication(); // UseAuthentication SHOULD ALWAYS BE BEFORE Authorization
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
The login Method in the Identity API:
// Log In ===================================================================================
[HttpPost]
[Route("LogIn")]
[AllowAnonymous]
public async Task<IActionResult> LogIn(string email, string password, bool rememberMe)
{
if(email != null && password !=null)
{
var result = await signInManager.PasswordSignInAsync(email, password, rememberMe, false);
if (result.Succeeded) // If Login Ok
{
return new JsonResult(result);
}
return StatusCode(401, "[\n \"Invalid Log In\" \n]"); // If Erors return errors
}
return StatusCode(401, "[\n \"Email or Password cant be empty\" \n]");
}
Using swagger I can get the cookie in the browser:
EDIT - Almost working: I added to the Identity API in the startup.cs this:
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
builder.SetIsOriginAllowed(_ => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
And in the Js Post Code:
// POST ----------------------------------------------------------------------------------
fetch(identityApiUri + postUrl + formQueryString, // #1 = API-Address, #2 = API - Controller/Mehod, #3 = form data as sring
{
method: 'POST',
mode: 'no-cors',
headers: {
'Access-Control-Allow-Origin': '*'
},
credentials: 'include'
And now I get the cookie but I get also exception: The server is returning Statuscode 200 and I can now get the cookie but I get exception in the fetch api post method.
Ok: false - but I get the cookie and the server returns status 200.
You're calling fetch()
from a different origin than the api, right? If so, this sounds like a simple CORS issue.
By default, CORS does not include credentials such as cookies. You have to opt in by both setting the credentials mode on the client side, and the Access-Control-Allow-Credentials
header on the server side.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
With the fetch()
api, the credentials mode is set with the credentials: 'include'
option. As far as the server side, I'm not familiar with ASP but it sounds like it provides some conveniency method to set the relevant header.
As you hint at in your post, when the Access-Control-Allow-Credentials
header is set to true
, the *
value - meaning any origin - actually can’t be used in the Access-Control-Allow-Origin
header, so you will have to be explicit about what origin you want allowed - ie the origin of your client application, the origin being defined as the combination of the protocol, domain, and port.