using Microsoft.IdentityModel.Tokens; using Microsoft.EntityFrameworkCore; using System.Text; using System.Security.Claims; using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography; using Microsoft.AspNetCore.Identity; using agologumApi.Models; public class JwtService { private readonly IConfiguration config_; private readonly AppDbContext db_; private readonly UserManager userManager_; public JwtService(IConfiguration config, AppDbContext db, UserManager userManager) { // why the heck does c# not have initializer lists ? config_ = config; db_ = db; userManager_ = userManager; } // create a jwt string given a user (user contains permissions which go into claims) public async Task GenerateJwt(User user) { // security stuff string? jwtKey = config_["Jwt:Key"]; if(jwtKey == null) return null; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // make sure the user is real if(user.UserName == null) return null; // not too sure var claims = new List { new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; // add each permission that the user has into the claims List? permissions = user.Permissions; if(permissions != null) { foreach(string perm in permissions) { claims.Add(new Claim("permission", perm)); } } // construct that token var token = new JwtSecurityToken( issuer: "agologum", audience: "agologum", claims: claims, expires: DateTime.UtcNow.AddHours(2), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } // generating a refresh token is just like a long random password public string GenerateRefreshToken() { byte[] randomBytes = new byte[64]; RandomNumberGenerator.Fill(randomBytes.AsSpan()); return Convert.ToBase64String(randomBytes); } // we store refresh tokens on our side to check against when a user requests a refresh public async Task GetRefreshToken(string refreshTokenString) { return await db_.RefreshTokens.FirstOrDefaultAsync(u => u.Token == refreshTokenString); } // add a refresh token to the token db store public async Task AddRefreshToken(RefreshToken refreshToken) { db_.RefreshTokens.Add(refreshToken); await db_.SaveChangesAsync(); return refreshToken; } // helper to get the User from the id that exists in a refresh token object public async Task GetUser(string id) { return await db_.Users.FindAsync(id); } // since other places aren't good for having references to db contexts // remove refresh token from our store; called when user logs out public async Task RevokeRefreshToken(string refreshTokenString) { var refreshToken = await db_.RefreshTokens.FirstOrDefaultAsync(u => u.Token == refreshTokenString); if(refreshToken == null) return false; refreshToken.IsRevoked = true; await db_.SaveChangesAsync(); return true; } }