I've tried all sorts of things, but I can't seem to figure out a way to work out how to make the token creator in PasetoService.cs work in harmony with the token validator in Startup.cs. It also gives a weird URL (http://localhost:8000/Account/Login?ReturnUrl=%2Ffruits) when I define the paseto authentication method in startup.cs.
I tried the following:
PasetoService.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using PasetoAuth.Entities;
using PasetoAuth.Models;
using ScottBrady.IdentityModel.Crypto;
using ScottBrady.IdentityModel.Tokens;
using ScottBrady.IdentityModel.Tokens.Paseto;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace PasetoAuth.Controllers;
public class PasetoService : IPasetoService
{
private readonly UserManager<PasetoUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
private readonly IConfiguration _configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
public PasetoService(UserManager<PasetoUser> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration)
{
this.userManager = userManager;
this.roleManager = roleManager;
_configuration = configuration;
}
public async Task<(int, string)> Registration(PasetoRegistrationModel model, string role)
{
var userExists = await userManager.FindByNameAsync(model.Username);
if (userExists != null)
return (0, "User already exists");
PasetoUser user = new()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.Username,
Name = model.Name
};
var createUserResult = await userManager.CreateAsync(user, model.Password);
if (!createUserResult.Succeeded)
return (0, "User creation failed! Please check user details and try again.");
if (!await roleManager.RoleExistsAsync(role))
await roleManager.CreateAsync(new IdentityRole(role));
if (await roleManager.RoleExistsAsync(PasetoUserRoles.User))
await userManager.AddToRoleAsync(user, role);
return (1, "User created successfully!");
}
public async Task<(int, string)> Login(PasetoLoginModel model)
{
var user = await userManager.FindByNameAsync(model.Username);
if (user == null)
return (0, "Invalid username");
if (!await userManager.CheckPasswordAsync(user, model.Password))
return (0, "Invalid password");
var userRoles = await userManager.GetRolesAsync(user);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
foreach (var userRole in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, userRole));
}
string token = GenerateToken(claims);
return (1, token);
}
private string GenerateToken(List<Claim> claims)
{
var handler = new PasetoTokenHandler();
var privateKey = Base64UrlEncoder.DecodeBytes(_configuration["Token:PrivateKey"]);
Dictionary<string, object> claimsDictionary = new Dictionary<string, object>();
foreach (Claim claim in claims)
{
claimsDictionary.Add(claim.Type, claim.Value);
}
string token = handler.CreateToken(new PasetoSecurityTokenDescriptor(PasetoConstants.Versions.V2, PasetoConstants.Purposes.Public)
{
Issuer = _configuration["Token:ValidIssuer"],
Audience = _configuration["Token:ValidAudience"],
Expires = DateTime.UtcNow.AddHours(1),
NotBefore = DateTime.UtcNow,
Claims = claimsDictionary,
SigningCredentials = new SigningCredentials(new EdDsaSecurityKey(EdDsa.Create(
new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { D = privateKey })), ExtendedSecurityAlgorithms.EdDsa)
});
return token;
}
}
Startup.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Paseto;
using PasetoAuth.Controllers;
using PasetoAuth.Entities;
using PasetoAuth.Models;
using PasetoBearer.Authentication;
namespace PasetoTestApi;
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<PasetoContext>(options => options.UseSqlServer(Configuration.GetConnectionString("PasetoServer")));
services.AddIdentity<PasetoUser, IdentityRole>().AddEntityFrameworkStores<PasetoContext>().AddDefaultTokenProviders();
services.AddAuthentication(PasetoBearerDefaults.AuthenticationScheme)
.AddPasetoBearer(options =>
{
options.PublicKey = Base64UrlEncoder.DecodeBytes(Configuration["Token:PublicKey"]);
options.PasetoTokenValidationParameters = new PasetoTokenValidationParameters()
{
ValidAudience = Configuration["Token:ValidAudience"],
ValidIssuer = Configuration["Token:ValidIssuer"],
};
options.Validate();
});
services.AddTransient<IPasetoService, PasetoService>();
}
public void Configure(WebApplication app)
{
if (!app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
}
}
appsettings.json
{
"ConnectionStrings": {
"PasetoServer": "Server=BRCACERV3571G01\\SQLEXPRESS01;Database=PasetoDb;User Id=sa;Password=xs2me$cite;TrustServerCertificate=True;"
},
"Token": {
"ValidAudience": "https://localhost:8000",
"ValidIssuer": "https://localhost:8000",
"PrivateKey": "YourPrivateKey",
"PublicKey": "YourPublicKey"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}