Basically I use an ASP.NET Core Web API, and the controller returns Ok when I send a request to Login, it sets the refresh and access tokens as http only cookies well.
But there's a problem: the user isn't authenticated (I mean that even if I try to go to a [Authorize]
route it won't work, or trying to access User
... with identity, still doesn't work) for some reason. I checked manually with JWT and the exp date, aud and issue are well set, I simply don't know what's the problem.
In the program.cs
new JwtBearerEvents
doesn't even print anything to the console. Please help me.
This is a part of the service for the controller:
public async Task<bool> Login(LoginUserModel user)
{
var identityUser = await _userManager.FindByNameAsync(user.UserName!);
if (identityUser is null)
{
return false;
}
return await _userManager.CheckPasswordAsync(identityUser, user.Password!);
}
public string GenerateTokenString(LoginUserModel user)
{
var tokenId = Guid.NewGuid().ToString(); // Unique identifier for each token
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.UserName !),
new Claim(ClaimTypes.Role, "Admin"), // This should be dynamic based on actual user role
new Claim("TokenId", tokenId) // Include the unique TokenId in the token
};
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(60), // Token validity period
Issuer = _config["Jwt:Issuer"],
Audience = _config["Jwt:Audience"],
SigningCredentials = signingCredentials
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public async Task<string> GenerateRefreshToken(string userName)
{
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
return null!;
// Create a new refresh token
var refreshToken = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(14); // Set refresh token validity
await _userManager.UpdateAsync(user);
return refreshToken;
}
This is the program.cs
:
using EasyLink.Server.Database.Context;
using EasyLink.Server.Identity;
using EasyLink.Server.Services.Auth;
using EasyLink.Server.Services.Categories;
using EasyLink.Server.Services.Stripe;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Stripe;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpClient();
// Configure DbContext.
builder.Services.AddDbContext<AuthDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Configure Identity.
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireDigit = true;
options.Password.RequireNonAlphanumeric = false;
}).AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
// Configure JWT Authentication.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("\n\n\n\n\n\n\nAuthentication failed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("\n\n\n\n\n\n\nToken validated successfully.");
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
Console.WriteLine("\n\n\n\nerror\n\n\n");
if (context.Request.Cookies.ContainsKey("AccessToken"))
{
context.Token = context.Request.Cookies["AccessToken"];
}
return Task.CompletedTask;
}
};
});
// Additional services.
builder.Services.AddTransient<IAuthService, AuthService>();
builder.Services.AddScoped<IStripeService, StripeService>();
builder.Services.AddHttpClient("CategoryApiClient", client =>
{
client.BaseAddress = new Uri("https://www.autovit.ro/api/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
builder.Services.AddScoped<ICategoriesService, CategoriesService>();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin", builder =>
{
builder.WithOrigins("https://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
StripeConfiguration.ApiKey = builder.Configuration["Stripe:Key"];
var app = builder.Build();
// Middleware pipeline configuration.
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHsts();
app.UseCors("AllowSpecificOrigin");
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("/index.html");
app.Run();
This is a part of the authcontroller:
[HttpPost("Login")]
public async Task<IActionResult> Login([FromBody] LoginUserModel user)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
if (!await _authService.CheckPayment(user.UserName!))
{
return Unauthorized("Payment required.");
}
if (await _authService.Login(user))
{
var tokenString = _authService.GenerateTokenString(user);
var refreshToken = await _authService.GenerateRefreshToken(user.UserName!);
// Store access token and refresh token in HttpOnly cookies
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddMinutes(60)
};
Response.Cookies.Append("AccessToken", tokenString, cookieOptions);
// Extend the cookie's expiry for refresh token since it should be valid longer than access token
var refreshCookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddDays(14) // Refresh token validity
};
Response.Cookies.Append("RefreshToken", refreshToken, refreshCookieOptions);
return Ok(new { message = "Login successful", accessToken = tokenString, refreshToken = refreshToken });
}
return Forbid("Invalid credentials.");
}
[HttpGet("verify")]
public IActionResult Verify()
{
var isAuthenticated = User.Identity!.IsAuthenticated;
return Ok(new { isAuthenticated });
}
I'm trying to Login a user...
Basically the Login returns ok but it still doesn't authenticate, I tried on [Authorize]
routes, and tries User.Identity!.IsAuthenticated;
and still returns false, the user is stored in the database well too..
Ok, so I found the problem myself, the problem was that I didn't set well the scheme, that's what you should modify:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
...
});
app.Use(async (context, next) =>
{
var token = context.Request.Cookies["AccessToken"];
if (!string.IsNullOrEmpty(token))
{
context.Request.Headers.Append("Authorization", $"Bearer {token}");
Console.WriteLine("Token appended to header: " + token);
}
else
{
Console.WriteLine("No token found in cookies");
}
await next();
});
fun fact, I found the problem when I fetched a wrong password for a valid email on the frontend :)))