add refresh tokens
All checks were successful
Build and Deploy API / build-and-deploy (push) Successful in 9s
All checks were successful
Build and Deploy API / build-and-deploy (push) Successful in 9s
This commit is contained in:
@@ -51,9 +51,18 @@ public class AuthController : ControllerBase {
|
||||
|
||||
if(!result.Succeeded) return Unauthorized();
|
||||
|
||||
var token = jwt_.GenerateJwt(user);
|
||||
var accessToken = jwt_.GenerateJwt(user);
|
||||
var refreshToken = jwt_.GenerateRefreshToken();
|
||||
RefreshToken newTokenObject = new RefreshToken {
|
||||
Token = refreshToken,
|
||||
UserId = user.Id,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
IsRevoked = false
|
||||
};
|
||||
await jwt_.AddRefreshToken(newTokenObject);
|
||||
|
||||
return Ok(new { token });
|
||||
return Ok(new { accessToken, refreshToken });
|
||||
}
|
||||
|
||||
[Authorize] // authorize is handled by middleware
|
||||
@@ -65,6 +74,37 @@ public class AuthController : ControllerBase {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("refresh")] // allow-anonymous by default
|
||||
public async Task<ActionResult> Refresh(TokenDto request) {
|
||||
|
||||
RefreshToken? storedToken = await jwt_.GetRefreshToken(request.RefreshToken);
|
||||
if (storedToken == null) return Unauthorized();
|
||||
bool valid = (storedToken.IsRevoked) ||
|
||||
(storedToken.ExpiresAt < DateTime.UtcNow);
|
||||
if(!valid) return Unauthorized(); // TODO: delete the invalid token
|
||||
|
||||
User? user = await jwt_.GetUser(storedToken.UserId);
|
||||
if(user == null) return NotFound();
|
||||
string? newAccessToken = jwt_.GenerateJwt(user);
|
||||
if(newAccessToken == null) return NotFound();
|
||||
string newRefreshToken = jwt_.GenerateRefreshToken();
|
||||
|
||||
storedToken.IsRevoked = true;
|
||||
RefreshToken newTokenObject = new RefreshToken {
|
||||
Token = newRefreshToken,
|
||||
UserId = storedToken.UserId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
IsRevoked = false
|
||||
};
|
||||
|
||||
await jwt_.AddRefreshToken(newTokenObject);
|
||||
|
||||
return Ok(new { accessToken = newAccessToken, refreshToken = newRefreshToken });
|
||||
|
||||
|
||||
}
|
||||
|
||||
// TODO
|
||||
// refresh tokens
|
||||
// email verification
|
||||
|
||||
@@ -12,5 +12,6 @@ public class AppDbContext : IdentityDbContext<User> {
|
||||
|
||||
// Db set for each model besides Users (DbSet<template> is already defined in IdenityDbContext<template>)
|
||||
public DbSet<Item> Items { get; set; }
|
||||
public DbSet<RefreshToken> RefreshTokens { get; set; }
|
||||
|
||||
}
|
||||
21
api/src/Models/RefreshToken.cs
Normal file
21
api/src/Models/RefreshToken.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
// a refresh token's purpose is to authenticate user's without logging in
|
||||
public class RefreshToken {
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Token { get; set; } = "";
|
||||
|
||||
public string UserId { get; set; } = ""; // in EF Identity the IdentityUser's id is a GUID string (32 hex digits)
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public bool IsRevoked { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TokenDto {
|
||||
|
||||
public string RefreshToken { get; set; } = "";
|
||||
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
using System.Security.Claims;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using agologumApi.Models;
|
||||
|
||||
public class JwtService {
|
||||
|
||||
private readonly IConfiguration config_;
|
||||
private readonly AppDbContext db_;
|
||||
|
||||
public JwtService(IConfiguration config) { // why the heck does c# not have initializer lists ?
|
||||
public JwtService(IConfiguration config, AppDbContext db) { // why the heck does c# not have initializer lists ?
|
||||
config_ = config;
|
||||
db_ = db;
|
||||
}
|
||||
|
||||
public string? GenerateJwt(User user) {
|
||||
@@ -42,5 +46,27 @@ public class JwtService {
|
||||
|
||||
}
|
||||
|
||||
public string GenerateRefreshToken() {
|
||||
|
||||
byte[] randomBytes = new byte[64];
|
||||
RandomNumberGenerator.Fill(randomBytes.AsSpan());
|
||||
return Convert.ToBase64String(randomBytes);
|
||||
|
||||
}
|
||||
|
||||
public async Task<RefreshToken?> GetRefreshToken(string refreshTokenString) {
|
||||
return await db_.RefreshTokens.FirstOrDefaultAsync(u => u.Token == refreshTokenString);
|
||||
}
|
||||
|
||||
public async Task<RefreshToken> 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<User?> GetUser(string id) {
|
||||
return await db_.Users.FindAsync(id);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user