Search code examples
c#authenticationjwtasp.net-core-mvcasp.net-core-webapi

ASP.NET Core MVC consuming API with JWT tokens. User.Identity.IsAuthenticated returning false


I created an API using .NET 7. This API uses JWT tokens and has a controller that takes care of registering and signing in users. This is working just fine in Swagger.

This is my API AuthController:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using WildlifeLogAPI.Models.DTO;
using WildlifeLogAPI.Repositories;

namespace WildlifeLogAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly UserManager<IdentityUser> userManager;
        private readonly ITokenRepository tokenRepository;

        public AuthController(UserManager<IdentityUser> userManager, ITokenRepository tokenRepository)
        {
            this.userManager = userManager;
            this.tokenRepository = tokenRepository;
        }

        //POST: api/Auth/Register
        [HttpPost]
        [Route("Register")]
        public async Task<IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto)
        {
            // create a new instance of IdentityUser (which includes the username and email they submit)
            var identityUser = new IdentityUser
            {
                UserName = registerRequestDto.Username,
                Email = registerRequestDto.Email
            };

            // then use the userManager's built-in CreateAsync (pass in the identityUser we just created, and the password passed into the dto)
            var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password);

            // Add roles to the user 

            // check to see if the user was successfully created 
            if (identityResult.Succeeded)
            {
                // Add the User Role by default when they register
                var roleIdentityResult = await userManager.AddToRoleAsync(identityUser, "User");

                // check if the userrole was successfully added, if so display message
                if (roleIdentityResult.Succeeded)
                {
                    return Ok("User was registered. Please login.");
                }
            }

            // if it didn't succeed 
            return BadRequest("something went wrong. Please try again. ");
        }

        //POST: api/auth/login 
        [HttpPost]
        [Route("Login")]
        public async Task<IActionResult> Login([FromBody] LoginRequestDto loginRequestDto)
        {
            // get the user by their email and store the user in the user variable
            var user = await userManager.FindByEmailAsync(loginRequestDto.Email);

            // check if the email is associated with a user
            // if it is filled in and we find it in the database then do the code required to log in 
            if (user != null)
            {
                // check that password matches the email using built in CheckPasswordAsync
                // pass in the email and password
                var checkPasswordResult = await userManager.CheckPasswordAsync(user, loginRequestDto.Password);

                // if the password matches the email, then create a token 
                if (checkPasswordResult)
                {
                    // Create token here 
                    // Get roles for this user 
                    var roles = await userManager.GetRolesAsync(user);
                    
                    // if there are roles then create the token 
                    if (roles != null)
                    {
                        // create jwt token
                        var jwtToken = tokenRepository.CreateJWTToken(user, roles.ToList());   // this is our token 

                        // put that token into a dto 
                        var response = new LoginResponseDto
                        {
                            jwtToken = jwtToken
                        };

                        return Ok(response);
                    }
                }
            }

            return BadRequest("Username or password is incorrect");
        }
    }
}

Then I created an ASP.NET Core MVC app with a controller called AuthsController that consumes that API and handles the JWT token.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using WildlifeLog.UI.Models.DTO;
using WildlifeLog.UI.Models.ViewModels;
using WildlifeLogAPI.Models.DTO;

namespace WildlifeLog.UI.Controllers
{
    public class AuthsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;
        private readonly IHttpContextAccessor httpContextAccessor;
        private readonly SignInManager<IdentityUser> signInManager;
        private readonly ILogger<AuthsController> logger;

        public AuthsController(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor,
            SignInManager<IdentityUser> signInManager, ILogger<AuthsController> logger)
        {
            this.httpClientFactory = httpClientFactory;
            this.httpContextAccessor = httpContextAccessor;
            this.signInManager = signInManager;
            this.logger = logger;
        }

        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
        {
            // Created client 
            var client = httpClientFactory.CreateClient();

            // create httpRequestMessage
            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("https://localhost:7075/api/auth/register"),
                Content = new StringContent(JsonSerializer.Serialize(registerViewModel), Encoding.UTF8, "application/json")
            };

            // use client to send httpRequestMessage to api and we get a json response abck 
            var httpResponseMessage = await client.SendAsync(httpRequestMessage);

            // Ensure success 
            httpResponseMessage.EnsureSuccessStatusCode();

            // "Read the body" as a string DONT convert form JSON to Dto 
            var response = await httpResponseMessage.Content.ReadAsStringAsync();

            // If successful, redirect to ? 
            if (response != null)
            {
                return RedirectToAction("Index", "Home");
            }

            // else just return to view 
            return View();
        }

        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel)
        {
            try
            {
                // create the client 
                var client = httpClientFactory.CreateClient();

                // create httpRequestMessage
                var httpRequestMessage = new HttpRequestMessage()
                {
                    Method = HttpMethod.Post,
                    RequestUri = new Uri("https://localhost:7075/api/auth/login"),
                    Content = new StringContent(JsonSerializer.Serialize(loginViewModel), Encoding.UTF8, "application/json")
                };
        
                // use client to send httpRequestMessage to api and we get a json response back 
                var httpResponseMessage = await client.SendAsync(httpRequestMessage);

                // Ensure success 
                httpResponseMessage.EnsureSuccessStatusCode();

                // "Read the body" as a string
                var response = await httpResponseMessage.Content.ReadAsStringAsync();

                // Deserialize the JSON response to a DTO (assuming LoginResponseDto is your DTO class)
                var loginResponseDto = JsonSerializer.Deserialize<Models.DTO.LoginResponseDto>(response);

                // Extract the JWT token from the response
                var jwtToken = loginResponseDto.jwtToken;

                // Store the token for subsequent requests (consider more secure storage options)
                HttpContext.Session.SetString("JwtToken", jwtToken);

                // Include the token in the Authorization header for subsequent requests
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);

                // Specify the authentication type when creating ClaimsIdentity
                var userIdentity = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.Email, loginViewModel.Email),
                    new Claim(ClaimTypes.AuthenticationMethod, "MyCookieMiddlewareInstance"),
                }, "MyCookieMiddlewareInstance");

                // Use ClaimsPrincipal with the specified ClaimsIdentity
                var user = new ClaimsPrincipal(userIdentity);

                // Use SignInAsync to sign in the user
                await HttpContext.SignInAsync("MyCookieMiddlewareInstance", user, new AuthenticationProperties
                {
                    ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
                    IsPersistent = false, // You can change this based on your requirements
                    AllowRefresh = false
                });

                // Log successful login
                logger.LogInformation("User successfully logged in.");

                return RedirectToAction("Index", "Home");
            }
            catch (HttpRequestException)
            {
                // Handle request exception (e.g., log, display error message)
                logger.LogError("Login failed due to HttpRequestException.");
                return View();
            }
            catch (Exception ex)
            {
                // Handle other exceptions
                logger.LogError(ex, "An unexpected error occurred during login.");
                return View();
            }
        }
    }
}

This is my Program.cs for the ASP.NET Core MVC app:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using WildlifeLogAPI.Data;
using WildlifeLogAPI;
using WildlifeLog.UI.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// Inject HttpClient
builder.Services.AddHttpClient();

// inject logging 
builder.Services.AddLogging(builder =>
{
    builder.AddConsole(); // Add other logging providers if needed
});

builder.Services.AddControllersWithViews();

// Inject Session Configuration 
builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    // Configure session options as needed
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.Name = "SessionCookie";
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// Inject dbContext 
builder.Services.AddDbContext<WildlifeLogDbContext>();
builder.Services.AddDbContext<WildlifeLogAuthDbContext>();

// inject identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<WildlifeLogAuthDbContext>()
    .AddDefaultTokenProviders();

// inject cloudinary 
builder.Services.AddScoped<IImageRepository, CloudinaryImageRepository>();

// Configure authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "MyCookieMiddlewareInstance";
    options.DefaultSignInScheme = "MyCookieMiddlewareInstance";
    options.DefaultChallengeScheme = "MyCookieMiddlewareInstance";
})
.AddCookie("MyCookieMiddlewareInstance", options =>
{
    options.ExpireTimeSpan = TimeSpan.FromMinutes(30); // Set your desired expiration time
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.SlidingExpiration = true;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseSession();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

When I log in on my UI, I can tell I am logged in because I can access restricted content vs when i am not logged in. So the Login works, however in my _layout view, I want to hide the Login and Register buttons when I am logged in and display the user's username. That is not happening even though I know I am logged in.

This is my _layout.cshtml:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> signInManager

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WildlifeLog.UI</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/WildlifeLog.UI.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container-fluid">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WildlifeLog.UI</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Parks" asp-action="Index">Parks</a>
                        </li>
                        <li>
                            <a class="nav-link text-dark" asp-area="" asp-controller="Logs" asp-action="Index">Your Logs</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="AdminUsers" asp-action="Index">Users</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                    </ul>
                    <div class="d-flex align-items-center">
                        @if (User.Identity.IsAuthenticated)
                        {
                            <div class="me-3 text-light">
                                @User?.Identity?.Name
                            </div>
                        }
                        else
                        {
                            <p>IsAuthenticated: @User.Identity.IsAuthenticated</p>
                        
                            <p> User is not signed in </p>
                            <a class="btn me-3 bg-light text-dark"
                               asp-area=""
                               asp-controller="Auths"
                               asp-action="Register">Register</a>

                            <a class="btn me-3 bg-light text-dark"
                               asp-area=""
                               asp-controller="Auths"
                               asp-action="Login">Login</a>
                        }
                    </div>
                </div>
            </div>

        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2023 - WildlifeLog.UI - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

So the User.Identity.IsAuthenticated is returning false even though I am logged in and can access controllers that can only be accessed by users that are logged in. What am I doing wrong?


Solution

  • So the User.Identity.IsAuthenticated is returning false even though I am logged in and can access controllers that can only be accessed by users that are logged in.

    An authentication scheme is a set of rules and handlers that are responsible for verifying a user's identity. In the code program you provided you used cookie authentication as well as configured identity, which customized the MyCookieMiddlewareInstance authentication scheme, however. In ASP.NET Core, the Identity authentication scheme is often considered the default scheme. In cases where the custom scheme is not set as the default authentication scheme, ASP.NET Core uses the Identity scheme instead of the custom scheme, which can cause User.Identity.IsAuthenticated to be returned incorrectly. Therefore, even if the user is logged in and authorized, it only uses the default authentication scheme of identity, not the custom authentication scheme. If you want to make sure that you use a custom cookie authentication scheme instead of the default Identity scheme, you can set the custom schema as the default authentication scheme:

    builder.Services.AddAuthentication(options =>
    {
       
        options.DefaultAuthenticateScheme = "MyCookieMiddlewareInstance";
        options.DefaultScheme = "MyCookieMiddlewareInstance";
        options.DefaultSignInScheme = "MyCookieMiddlewareInstance";
        options.DefaultChallengeScheme = "MyCookieMiddlewareInstance";
    })
    

    User.Identity.IsAuthenticated can be returned correctly:enter image description here