Files
pos-system/services/iam-service-net/docs/en

IAM Service .NET 10

Identity and Access Management Service built with .NET 10, ASP.NET Core Identity, and Duende IdentityServer following DDD, CQRS, and Clean Architecture patterns.

Overview

This service provides OAuth2/OpenID Connect authentication and authorization:

  • OAuth2/OIDC Server - Duende IdentityServer for token management
  • User Management - Registration, profile, soft-delete
  • Role-Based Access Control - User roles and permissions
  • Token Management - Access (15 min), Refresh (7 days) tokens
  • Email Verification - SMTP-based email confirmation
  • Two-Factor Authentication (2FA) - TOTP with QR code setup
  • Social Login - Google and Facebook OAuth integration
  • CQRS Pattern - MediatR for Commands/Queries
  • Clean Architecture - Domain, Infrastructure, API layers

Tech Stack

Technology Purpose
.NET 10 Runtime
ASP.NET Core Identity User/Role management
Duende IdentityServer OAuth2/OIDC server
EF Core + PostgreSQL Data persistence
Redis Distributed caching
MediatR CQRS pattern
FluentValidation Request validation
Serilog Structured logging

Quick Start

1. Prerequisites

  • .NET SDK 10.0.101+
  • Docker (for PostgreSQL)

2. Configure Environment

cp .env.example .env
# Edit DATABASE_URL, JWT_SECRET in .env

3. Run with Docker

docker-compose up -d

Service available at: http://localhost:5001

4. Run Locally

dotnet restore
dotnet build
dotnet run --project src/IamService.API

Database Migrations

Prerequisites

# Install EF Core tools (one-time)
dotnet tool install --global dotnet-ef

Create Migration

dotnet ef migrations add <MigrationName> \
  --project src/IamService.Infrastructure \
  --startup-project src/IamService.API

Apply Migration

dotnet ef database update \
  --project src/IamService.Infrastructure \
  --startup-project src/IamService.API

Neon Database Setup

  1. Create database on Neon Console
  2. Update appsettings.Development.json with connection string
  3. Run: dotnet ef database update ...

API Endpoints

Authorization Policies

Note: All API endpoints require authentication (Bearer JWT Token).
Some endpoints require specific roles as shown below.

Policy Required Role Applied To
RequireSuperAdmin SuperAdmin PAM (Privileged Access Management)
RequireAdmin Admin, SuperAdmin User/Role/Group/Organization management
RequireAuditor Auditor, Admin, SuperAdmin Audit logs, Compliance reports
OwnerOrAdmin Owner or Admin User self-service profile management

Authorization by Controller:

Controller Policy Description
Users (GET /users, DELETE) RequireAdmin List users, delete user
Users (GET/PUT /{id}) OwnerOrAdmin User access own profile or Admin access any
Roles RequireAdmin Role management
Organizations RequireAdmin Organization management
Groups RequireAdmin Group management
Access Requests RequireAdmin Access request workflow
Access Reviews RequireAdmin Periodic access review
Privileged Access RequireSuperAdmin PAM - most sensitive
Audit RequireAuditor View audit logs
Compliance RequireAuditor Compliance reports
Verifications RequireAdmin Identity verification

Authentication (/api/v1/auth)

Method Endpoint Description Auth
POST /api/v1/auth/register Register new user
POST /connect/token OAuth2 token endpoint
POST /api/v1/auth/change-password Change password
POST /api/v1/auth/logout Logout (revoke tokens)

Email Verification (/api/v1/auth)

Method Endpoint Description Auth
POST /api/v1/auth/send-verification-email Send email verification link
POST /api/v1/auth/confirm-email Confirm email with token

Two-Factor Authentication (/api/v1/auth/2fa)

Method Endpoint Description Auth
POST /api/v1/auth/2fa/enable Enable 2FA (get QR code)
POST /api/v1/auth/2fa/verify Verify TOTP code & activate
POST /api/v1/auth/2fa/disable Disable 2FA

Social Login (/api/v1/auth)

Method Endpoint Description Auth
GET /api/v1/auth/external-login/{provider} Initiate OAuth flow (Google/Facebook)
GET /api/v1/auth/external-callback Handle OAuth callback
GET /api/v1/auth/linked-accounts Get linked OAuth providers

User Management (/api/v1/users)

Method Endpoint Description Auth
GET /api/v1/users List users (paginated)
GET /api/v1/users/me Get current user
GET /api/v1/users/{id} Get user by ID
PUT /api/v1/users/{id} Update user
DELETE /api/v1/users/{id} Delete user (soft)

Health Endpoints

Endpoint Purpose
/health Full health status
/health/live Liveness probe
/health/ready Readiness probe

Organizations (/api/v1/organizations) - Phase 2

Method Endpoint Description Auth
GET /api/v1/organizations/{id} Get organization by ID
GET /api/v1/organizations/slug/{slug} Get organization by slug
POST /api/v1/organizations Create organization
PUT /api/v1/organizations/{id} Update organization
DELETE /api/v1/organizations/{id} Archive organization
GET /api/v1/organizations/{id}/hierarchy Get hierarchy
GET /api/v1/organizations/{id}/children Get child orgs

Groups (/api/v1/groups) - Phase 2

Method Endpoint Description Auth
GET /api/v1/groups List groups
GET /api/v1/groups/{id} Get group by ID
POST /api/v1/groups Create group
DELETE /api/v1/groups/{id} Delete group
POST /api/v1/groups/{id}/members Add member
DELETE /api/v1/groups/{id}/members/{userId} Remove member

Access Requests (/api/v1/access-requests) - Phase 3A

Method Endpoint Description Auth
POST /api/v1/access-requests Create access request
GET /api/v1/access-requests List requests
GET /api/v1/access-requests/{id} Get request by ID
POST /api/v1/access-requests/{id}/submit Submit request
POST /api/v1/access-requests/{id}/approve Approve
POST /api/v1/access-requests/{id}/reject Reject
DELETE /api/v1/access-requests/{id} Cancel request
GET /api/v1/access-requests/pending Pending requests

Access Reviews (/api/v1/access-reviews) - Phase 3B

Method Endpoint Description Auth
POST /api/v1/access-reviews Create access review
GET /api/v1/access-reviews/{id} Get review by ID
POST /api/v1/access-reviews/{id}/items Add item
POST /api/v1/access-reviews/{id}/start Start review
POST /api/v1/access-reviews/{id}/items/{itemId}/review Certify/Revoke
POST /api/v1/access-reviews/{id}/complete Complete

Privileged Access (/api/v1/privileged-access) - Phase 3B PAM

Method Endpoint Description Auth
POST /api/v1/privileged-access/request Request JIT access
GET /api/v1/privileged-access/active Active grants
POST /api/v1/privileged-access/{id}/revoke Revoke access

Audit (/api/v1/audit) - Phase 4A

Method Endpoint Description Auth
GET /api/v1/audit/logs Get audit logs (filtered)

Compliance (/api/v1/compliance) - Phase 4A

Method Endpoint Description Auth
POST /api/v1/compliance/reports Generate report
GET /api/v1/compliance/reports List reports
GET /api/v1/compliance/reports/{id} Report detail
POST /api/v1/compliance/reports/{id}/complete Complete report
GET /api/v1/compliance/violations Unresolved violations

Authentication Flow

Step 1: Register a New User

curl -X POST http://localhost:5001/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "Password123!",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+84901234567"
  }'

Response:

{
  "success": true,
  "data": {
    "userId": "8083b412-5040-4b74-baf0-5cb709861957",
    "email": "user@example.com",
    "fullName": "John Doe"
  },
  "error": null,
  "pagination": null
}

Step 2: Login (Password Grant)

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=password-client" \
  -d "client_secret=password-client-secret" \
  -d "username=user@example.com" \
  -d "password=Password123!" \
  -d "scope=openid profile email api offline_access"

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjlGMzk1RDQzMDIz...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "5B8FB8189575241FF63C32ED2D0D492B32DA533B...",
  "scope": "api email offline_access openid profile"
}

Step 3: Use Access Token

curl http://localhost:5001/api/v1/users/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Step 4: Refresh Token

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=eyJhbGciOiJSUzI1NiIs..."

Step 5: Logout

curl -X POST http://localhost:5001/api/v1/auth/logout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Client Credentials (Service-to-Service)

For service-to-service authentication without user context:

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=goodgo-service" \
  -d "client_secret=service-secret" \
  -d "scope=api"

Supported OAuth2 Grant Types

Grant Type Use Case Requires User
password User login from trusted apps Yes
refresh_token Token renewal No (uses refresh token)
client_credentials Service-to-service No

Email Verification

Send Verification Email

curl -X POST http://localhost:5001/api/v1/auth/send-verification-email \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

Response:

{
  "success": true,
  "data": {
    "success": true,
    "message": "Verification email sent successfully."
  },
  "error": null,
  "pagination": null
}

Confirm Email

curl -X POST http://localhost:5001/api/v1/auth/confirm-email \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "token": "confirmation-token-from-email"}'

Two-Factor Authentication (2FA)

Enable 2FA

curl -X POST http://localhost:5001/api/v1/auth/2fa/enable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
  "success": true,
  "data": {
    "qrCodeBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAooAAAKK...",
    "manualEntryKey": "3R36NBSHHFGRTZLMNSOH5Q2FPETPRYMP",
    "recoveryCodes": [
      "KEJF-MPAY",
      "URMF-YRVX",
      "GPK4-G9WX",
      "LS34-FNP8",
      "WRM0-WAML",
      "QY5W-VVER",
      "AIEU-GLD6",
      "FDJL-3HNY",
      "J1MC-JXXT",
      "8GVA-OAAM"
    ]
  },
  "error": null,
  "pagination": null
}

Verify 2FA Code

curl -X POST http://localhost:5001/api/v1/auth/2fa/verify \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'

Disable 2FA

curl -X POST http://localhost:5001/api/v1/auth/2fa/disable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'

Social Login

Initiate OAuth Flow

Redirect user to:

GET http://localhost:5001/api/v1/auth/external-login/Google?returnUrl=http://your-app/callback
GET http://localhost:5001/api/v1/auth/external-login/Facebook?returnUrl=http://your-app/callback

Get Linked Accounts

curl http://localhost:5001/api/v1/auth/linked-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
  "success": true,
  "data": {
    "linkedProviders": [
      {"provider": "Google", "providerDisplayName": "Google"},
      {"provider": "Facebook", "providerDisplayName": "Facebook"}
    ]
  }
}

Configuration

Environment Variables

Variable Description Required
ASPNETCORE_ENVIRONMENT Environment No (default: Development)
DATABASE_URL PostgreSQL connection Yes
JWT_SECRET JWT signing secret (32+ chars) Yes
REDIS_HOST Redis server host No (default: localhost)
REDIS_PORT Redis server port No (default: 6379)
REDIS_PASSWORD Redis password No
REDIS_DATABASE Redis database number No (default: 0)

Token Lifetimes

Token Lifetime
Access Token 15 minutes
Refresh Token 7 days

Redis Caching

The service uses Redis for distributed caching with the ICacheService interface.

Configuration

Add Redis settings in appsettings.json:

{
  "Redis": {
    "Host": "localhost",
    "Port": 6379,
    "Password": "",
    "Database": 0,
    "ConnectTimeout": 5000,
    "SyncTimeout": 5000
  }
}

Or use environment variables:

REDIS_HOST=your-redis-host
REDIS_PORT=6379
REDIS_PASSWORD=your-password
REDIS_DATABASE=0

ICacheService Interface

public interface ICacheService
{
    // Basic operations
    Task<T?> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    Task RemoveAsync(string key);
    Task<bool> ExistsAsync(string key);
    
    // Get or create pattern
    Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
    
    // Token blacklist support
    Task BlacklistAsync(string key, TimeSpan expiration);
    Task<bool> IsBlacklistedAsync(string key);
}

Usage Examples

Basic Get/Set:

// Inject ICacheService
public class MyService
{
    private readonly ICacheService _cache;
    
    public MyService(ICacheService cache) => _cache = cache;
    
    public async Task<User?> GetUser(string userId)
    {
        return await _cache.GetAsync<User>($"user:{userId}");
    }
    
    public async Task CacheUser(User user)
    {
        await _cache.SetAsync($"user:{user.Id}", user, TimeSpan.FromMinutes(15));
    }
}

Get or Set Pattern (Cache-Aside):

public async Task<User> GetUserById(string userId)
{
    return await _cache.GetOrSetAsync(
        $"user:{userId}",
        async () => await _repository.GetByIdAsync(userId),
        TimeSpan.FromMinutes(15)
    );
}

Token Blacklisting (for Logout):

public async Task Logout(string tokenId)
{
    // Blacklist the refresh token for its remaining lifetime
    await _cache.BlacklistAsync($"token:{tokenId}", TimeSpan.FromDays(7));
}

public async Task<bool> IsTokenRevoked(string tokenId)
{
    return await _cache.IsBlacklistedAsync($"token:{tokenId}");
}

### Password Policy

- Minimum 8 characters
- Requires: uppercase, lowercase, digit, special character

## Project Structure

iam-service-net/ ├── src/ │ ├── IamService.API/ # Controllers, CQRS │ │ ├── Controllers/ # AuthController, UsersController │ │ └── Application/ # Commands, Queries, Validations │ ├── IamService.Domain/ # Domain entities │ │ ├── AggregatesModel/ # UserAggregate, RoleAggregate │ │ ├── Events/ # Domain events │ │ └── Exceptions/ # Domain exceptions │ └── IamService.Infrastructure/ # Data access │ ├── IamServiceContext.cs # DbContext with Identity │ └── Repositories/ # Repository implementations ├── tests/ │ ├── IamService.UnitTests/ │ └── IamService.FunctionalTests/ ├── docs/ │ ├── en/ # English documentation │ └── vi/ # Vietnamese documentation ├── Dockerfile └── docker-compose.yml


## Swagger UI

After running the service, access Swagger UI at:
- **Local**: http://localhost:5001/swagger
- **Docker**: http://localhost/api/v1/iam/swagger

## Testing

```bash
# Run all tests
dotnet test

# Run with coverage
dotnet test /p:CollectCoverage=true

Docker

# Build image
docker build -t goodgo/iam-service:latest .

# Run container
docker run -p 5001:8080 --env-file .env goodgo/iam-service:latest

Resources

License

Proprietary - GoodGo Platform