Skip to main content

Overview

The Identity service manages user authentication and authorization. It handles user registration, password management, and JWT token issuance for accessing protected resources across the platform. Port: 50000 (external), 5000 (internal)
Database Schema: bc_identity
Dependencies: PostgreSQL

Responsibilities

  • User registration and account creation
  • Password hashing and verification
  • JWT token generation and validation
  • User credential management
  • Serve as the authentication authority for all microservices

API Endpoints

Endpoints are defined using minimal API style.

Issue Token (Login)

POST /token
endpoint
Authenticates a user and issues a JWT token for subsequent API requests.
Request Body:
Email
string
required
User’s email address
Password
string
required
User’s password
~/workspace/source/services/identity/src/Identity.Api/Program.cs
app.MapPost("/token", async (
    IssueTokenRequest request,
    IssueTokenHandler handler) =>
{
    var result = await handler.Handle(
        new IssueTokenCommand(request.Email, request.Password));

    return Results.Ok(result);
});

public record IssueTokenRequest(string Email, string Password);
Response (200 OK):
Token
string
JWT access token
ExpiresAt
DateTime
Token expiration timestamp (typically 2 hours from issuance)
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": "2026-03-04T22:30:00Z"
}
Error Response (500):
{
  "error": "Invalid credentials"
}

Domain Models

User

Represents a user account in the system.
Id
Guid
Unique user identifier (auto-generated)
Email
string
User’s email address (unique)
PasswordHash
string
Hashed password (never stored in plain text)
~/workspace/source/services/identity/src/Identity.Domain/Entities/User.cs
public class User
{
    public Guid Id { get; private set; }
    public string Email { get; private set; } = string.Empty;
    public string PasswordHash { get; private set; } = string.Empty;

    private User() { }

    public User(string email, string passwordHash)
    {
        Id = Guid.NewGuid();
        Email = email;
        PasswordHash = passwordHash;
    }
}

Configuration

Database Connection

appsettings.json
{
  "ConnectionStrings": {
    "Default": "Host=postgres;Port=5432;Database=ticketing;Username=postgres;Password=postgres;SearchPath=bc_identity"
  }
}

JWT Configuration

{
  "Jwt": {
    "Key": "dev-secret-key-minimum-32-chars-required-for-security",
    "Issuer": "SpecKit.Identity",
    "Audience": "SpecKit.Services"
  }
}
JWT Settings:
  • Key: Secret key for signing tokens (must be at least 32 characters)
  • Issuer: Token issuer identifier
  • Audience: Token audience (typically the service ecosystem)
  • Expiry: Tokens expire 2 hours after issuance

Use Cases

IssueToken

Authenticates a user and issues a JWT token.
~/workspace/source/services/identity/src/Identity.Application/UseCases/IssueToken/IssueTokenHandler.cs
public async Task<TokenResult> Handle(IssueTokenCommand command)
{
    // Find user by email
    var user = await _userRepository.GetByEmailAsync(command.Email);

    if (user is null)
        throw new Exception("Invalid credentials");

    // Verify password matches stored hash
    if (!_passwordHasher.VerifyPassword(command.Password, user.PasswordHash))
        throw new Exception("Invalid credentials");

    // Generate JWT token
    var expiresAt = DateTime.UtcNow.AddHours(2);
    var token = _tokenGenerator.Generate(user);

    return new TokenResult(token, expiresAt);
}

CreateUser

Registers a new user account.
~/workspace/source/services/identity/src/Identity.Application/UseCases/CreateUser/CreateUserHandler.cs
public async Task<Guid> Handle(CreateUserCommand command)
{
    // Verify user doesn't already exist
    var existingUser = await _userRepository.GetByEmailAsync(command.Email);
    if (existingUser is not null)
        throw new Exception($"User with email {command.Email} already exists");

    // Hash the password
    var passwordHash = _passwordHasher.HashPassword(command.Password);

    // Create user entity
    var user = new User(command.Email, passwordHash);

    // Save to database
    await _userRepository.SaveAsync(user);

    return user.Id;
}

Ports (Interfaces)

The Identity service defines several domain ports:

IUserRepository

Repository for user persistence.
public interface IUserRepository
{
    Task<User?> GetByEmailAsync(string email);
    Task SaveAsync(User user);
}

IPasswordHasher

Password hashing and verification.
public interface IPasswordHasher
{
    string HashPassword(string password);
    bool VerifyPassword(string password, string hash);
}
Implementation: Uses BCrypt or similar secure hashing algorithm.

ITokenGenerator

JWT token generation.
public interface ITokenGenerator
{
    string Generate(User user);
}
Implementation: Generates JWT with user claims (Id, Email) signed with configured secret key.

IDbInitializer

Database initialization and seeding.
public interface IDbInitializer
{
    Task InitializeAsync();
}

Database Initialization

On service startup, the Identity service:
  1. Runs database migrations to create schema and tables
  2. Seeds a test user for development:
Program.cs (startup)
using (var scope = app.Services.CreateScope())
{
    // Initialize database schema
    var dbInitializer = scope.ServiceProvider.GetRequiredService<IDbInitializer>();
    await dbInitializer.InitializeAsync();

    // Seed test user
    var createUserHandler = scope.ServiceProvider.GetRequiredService<CreateUserHandler>();
    await createUserHandler.Handle(new CreateUserCommand("[email protected]", "Password123!"));
    Console.WriteLine("✓ Usuario de prueba creado: [email protected]");
}

JWT Token Structure

Generated JWT tokens include the following claims:
  • sub (subject): User ID (GUID)
  • email: User’s email address
  • iss (issuer): SpecKit.Identity
  • aud (audience): SpecKit.Services
  • exp (expiration): Timestamp (2 hours from issuance)
  • iat (issued at): Timestamp

Authentication Flow

  1. User submits credentials: POST to /token with email and password
  2. Validate user exists: Query database for user by email
  3. Verify password: Hash provided password and compare with stored hash
  4. Generate token: Create JWT with user claims and 2-hour expiry
  5. Return token: Send JWT and expiry timestamp to client
  6. Client stores token: Typically in localStorage or httpOnly cookie
  7. Subsequent requests: Client includes JWT in Authorization: Bearer <token> header
  8. Services validate token: Other services verify JWT signature and claims

Security Considerations

  • Password Hashing: Passwords are hashed using BCrypt (or similar) before storage
  • No plain-text passwords: Passwords are never stored or logged in plain text
  • JWT expiration: Tokens expire after 2 hours to limit exposure window
  • Secret key management: JWT signing key should be rotated regularly in production
  • HTTPS: All authentication endpoints should be served over HTTPS in production

Architecture Notes

  • Uses Minimal APIs for endpoint registration (no controllers)
  • Uses Ports and Adapters pattern for infrastructure concerns
  • Infrastructure services registered via AddInfrastructure() extension method
  • Database initialization runs automatically on service startup
  • Supports seeding test users for development environments

Integration with Other Services

Other microservices validate JWT tokens issued by the Identity service:
  1. Extract JWT from Authorization header
  2. Verify signature using shared JWT secret key
  3. Validate issuer, audience, and expiration claims
  4. Extract user ID and email from token claims
  5. Proceed with authorized request
JWT configuration must match across all services:
{
  "Jwt": {
    "Key": "dev-secret-key-minimum-32-chars-required-for-security",
    "Issuer": "SpecKit.Identity",
    "Audience": "SpecKit.Services"
  }
}

Build docs developers (and LLMs) love