Search code examples
javascriptauthenticationcookiesfetch-api

Fetch API - Can't get the Login cookie from ASP Identity API


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: Img1

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.

img2

Ok: false - but I get the cookie and the server returns status 200. img4


Solution

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