I am sorry I must be missing something very obvious, but why does the Logout endpoint always returns true?
AuthenticationController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Registry_Backend.DTO;
using Registry_Backend.Models;
using Registry_Backend.Shared;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using System.Text;
namespace Registry_Backend.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class AuthenticationController : ControllerBase
{
private readonly SignInManager<IdentityUser> signInManager;
private readonly RegistryContext dbContext;
private readonly UserManager<IdentityUser> userManager;
public AuthenticationController(RegistryContext dbContext, SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
this.dbContext = dbContext;
this.signInManager = signInManager;
this.userManager = userManager;
}
[HttpPost("Login")]
[ProducesResponseType(typeof(JWTTokenResponse), StatusCodes.Status200OK)]
public async Task<IActionResult> LoginAsync([FromBody] LoginData loginData)
{
if (loginData is null)
{
throw new AppException("Invalid user request!!!");
}
else
{
var result = await signInManager.PasswordSignInAsync(loginData.UserName, loginData.Password, false, false);
if (result.Succeeded)
{
var user = await userManager.FindByNameAsync(loginData.UserName);
if (user != null) {
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManagerExtension.AppSetting["JWT:Secret"]));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: ConfigurationManagerExtension.AppSetting["JWT:ValidIssuer"],
audience: ConfigurationManagerExtension.AppSetting["JWT:ValidAudience"], claims: new List<Claim>(),
expires: DateTime.Now.AddMinutes(6),
signingCredentials: signinCredentials);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new JWTTokenResponse
{
Token = tokenString,
UserId = user.Id
});
}
}
}
return Unauthorized();
}
[HttpPost("Logout")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
public async Task<IActionResult> LogoutAsync()
{
await signInManager.SignOutAsync();
return Ok("OK");
}
[HttpGet("IsLoggedIn")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
public async Task<IActionResult> IsLoggedIn([FromQuery] string userId)
{
var user = await userManager.FindByIdAsync(userId);
if(user != null)
{
var claims = await signInManager.CreateUserPrincipalAsync(user);
return Ok(signInManager.IsSignedIn(claims));
}
return Ok(false);
}
}
}
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Registry_Backend;
using Registry_Backend.Models;
using Registry_Backend.Shared;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => {
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Name = "Authorization",
Description = "Bearer Authentication with JWT Token",
Type = SecuritySchemeType.Http
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
new List < string > ()
}
});
});
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
builder.Services.AddDbContext<RegistryContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
options.Lockout.AllowedForNewUsers = false
).AddEntityFrameworkStores<RegistryContext>();
builder.Services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = ConfigurationManagerExtension.AppSetting["JWT:ValidIssuer"],
ValidAudience = ConfigurationManagerExtension.AppSetting["JWT:ValidAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManagerExtension.AppSetting["JWT:Secret"]))
};
});
var app = builder.Build();
app.UseCors("AllowAll");
app.UseMiddleware<ErrorHandlerMiddleware>();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Instead of using signInManager.CreateUserPrincipalAsync()
, you can get HttpContext.User
to see if a user is currently logged in.
Inject IHttpContextAccessor
into the controller to get the current user:
public class AuthenticationController : ControllerBase
{
private readonly SignInManager<IdentityUser> signInManager;
private readonly RegistryContext dbContext;
private readonly UserManager<IdentityUser> userManager;
private IHttpContextAccessor httpContextAccessor;
public AuthenticationController(RegistryContext dbContext, SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IHttpContextAccessor httpContextAccessor)
{
this.dbContext = dbContext;
this.signInManager = signInManager;
this.userManager = userManager;
this.httpContextAccessor = httpContextAccessor;
}
//...
[HttpGet("IsLoggedIn")]
public async Task<IActionResult> IsLoggedIn([FromQuery] string userId)
{
var user = await userManager.FindByIdAsync(userId);
if (user != null)
{
var claims = httpContextAccessor.HttpContext.User;
return Ok(signInManager.IsSignedIn(claims));
}
return Ok(false);
}
}
The current user Id can be obtained by:
var claims = httpContextAccessor.HttpContext.User;
if (claims.FindFirst(ClaimTypes.NameIdentifier) != null)
{
var Id = claims.FindFirst(ClaimTypes.NameIdentifier).Value;
}
Edit:
When using JWT token authentication and validate the token, the server will get the token from the request header with the 'Authentication' key, after that validate it. If the token is valid, the user can continue accessing the resource, otherwise it will show the not permission notification message.
You can add the custom middleware to add the JWT token at the request header like below.
In Program.cs, add these lines:
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
//Before app.UseAuthentication();
app.UseSession();
app.Use(async (context, next) =>
{
var JWToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(JWToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
}
await next();
});
app.UseAuthentication();
app.UseAuthorization();
Then in your controller:
[HttpPost("Login")]
[ProducesResponseType(typeof(JWTTokenResponse), StatusCodes.Status200OK)]
public async Task<IActionResult> LoginAsync([FromBody] LoginData loginData)
{
//...
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
//Add this line
HttpContext.Session.SetString("JWToken", tokenString);
return Ok(new JWTTokenResponse
{
Token = tokenString,
UserId = user.Id,
Role = role
});
//...
}