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">
© 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?
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";
})