C# / ASP.NET
Implement Epilogue Auth with C# and ASP.NET Core
Installation
dotnet new webapi -n MyApp
cd MyApp
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServerConfiguration
Add to appsettings.json:
{
"EpilogueAuth": {
"AuthUrl": "https://auth.epilogue.team/authorize/your-app-id",
"AppId": "your-app-id",
"AppSecret": "your-app-secret"
}
}Models
// Models/User.cs
public class User
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string AuthUserId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? ProfileImage { get; set; }
public ICollection<Session> Sessions { get; set; } = new List<Session>();
}
// Models/Session.cs
public class Session
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Token { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public User User { get; set; } = null!;
public DateTime ExpiresAt { get; set; }
}DTOs
// DTOs/EpilogueAuthDtos.cs
public record TokenRequest(string AuthorizationCode, string ApplicationSecret);
public record TokenResponse(string Token);
public record EpilogueUser(string Id, string Username, string? IconUrl);DbContext
// Data/AppDbContext.cs
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
public DbSet<Session> Sessions => Set<Session>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasIndex(u => u.AuthUserId)
.IsUnique();
modelBuilder.Entity<Session>()
.HasIndex(s => s.Token)
.IsUnique();
}
}Auth Controller
// Controllers/AuthController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
private readonly AppDbContext _db;
private readonly IConfiguration _config;
private readonly HttpClient _http;
public AuthController(AppDbContext db, IConfiguration config, HttpClient http)
{
_db = db;
_config = config;
_http = http;
}
[HttpGet("start")]
public IActionResult Start()
{
var authUrl = _config["EpilogueAuth:AuthUrl"];
return Redirect(authUrl!);
}
[HttpGet("callback")]
public async Task<IActionResult> Callback([FromQuery] string? code)
{
if (string.IsNullOrEmpty(code))
return BadRequest(new { error = "Missing code" });
try
{
var appId = _config["EpilogueAuth:AppId"];
var appSecret = _config["EpilogueAuth:AppSecret"];
// Exchange code for token
var tokenRequest = new TokenRequest(code, appSecret!);
var tokenResponse = await _http.PostAsJsonAsync(
$"https://auth.epilogue.team/api/v1/authorize/{appId}",
tokenRequest
);
var tokenData = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>();
// Fetch user info
_http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenData!.Token);
var userResponse = await _http.GetFromJsonAsync<EpilogueUser>(
"https://auth.epilogue.team/api/v1/app/me"
);
// Find or create user
var user = await _db.Users.FirstOrDefaultAsync(u => u.AuthUserId == userResponse!.Id);
if (user == null)
{
user = new User
{
AuthUserId = userResponse!.Id,
Name = userResponse.Username ?? $"user_{userResponse.Id}",
ProfileImage = userResponse.IconUrl
};
_db.Users.Add(user);
await _db.SaveChangesAsync();
}
// Create session
var sessionToken = GenerateRandomString(200);
var session = new Session
{
Token = sessionToken,
UserId = user.Id,
ExpiresAt = DateTime.UtcNow.AddDays(1)
};
_db.Sessions.Add(session);
await _db.SaveChangesAsync();
// Return token to client
return Content($@"
<html><body><script>
localStorage.setItem('token', '{sessionToken}');
window.location.href = '/';
</script></body></html>
", "text/html");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return StatusCode(500, new { error = "Internal server error" });
}
}
[HttpGet("me")]
public async Task<IActionResult> Me()
{
var token = Request.Headers.Authorization.ToString().Replace("Bearer ", "")
?? Request.Query["token"].ToString();
if (string.IsNullOrEmpty(token))
return Unauthorized(new { error = "Authentication required" });
var session = await _db.Sessions
.Include(s => s.User)
.FirstOrDefaultAsync(s => s.Token == token && s.ExpiresAt > DateTime.UtcNow);
if (session == null)
return Unauthorized(new { error = "Invalid or expired token" });
return Ok(new
{
id = session.User.Id,
name = session.User.Name,
profileImage = session.User.ProfileImage
});
}
private static string GenerateRandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Range(0, length)
.Select(_ => chars[RandomNumberGenerator.GetInt32(chars.Length)])
.ToArray());
}
}Program.cs
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddHttpClient();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
app.MapControllers();
app.Run();