- Added a fixed issuer URI for inter-service communication in the docker-compose.yml and appsettings.json. - Updated DependencyInjection to utilize the new issuer URI, ensuring consistency across hosts and containers.
IAM Service .NET
Identity and Access Management Service built with .NET 10, ASP.NET Core Identity, and OpenIddict.
Overview
IAM Service provides OAuth2/OpenID Connect authentication and authorization capabilities:
- User Management: Registration, profile management, account locking
- Role-Based Access Control (RBAC): Role assignment, permission management
- OAuth2 Token Endpoints: Password, Refresh Token, Client Credentials grants
- JWT Tokens: Access tokens (15 min), Refresh tokens (7 days)
Tech Stack
| Technology | Purpose |
|---|---|
| .NET 10 | Runtime |
| ASP.NET Core Identity | User/Role management |
| OpenIddict | OAuth2/OIDC server |
| EF Core + PostgreSQL | Data persistence |
| MediatR | CQRS pattern |
| FluentValidation | Request validation |
| Serilog | Structured logging |
Quick Start
1. Prerequisites
- .NET SDK 10.0.101+
- Docker (for PostgreSQL)
2. Setup Environment
cp .env.example .env
# Edit DATABASE_URL in .env
3. Run with Docker Compose
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
- Create database on Neon Console
- Copy connection string
- Update
appsettings.Development.json:
{
"ConnectionStrings": {
"DefaultConnection": "Host=<host>;Port=5432;Database=<db>;Username=<user>;Password=<pass>;SSL Mode=Require"
}
}
- Run migrations:
dotnet ef database update ...
API Endpoints
Authentication (/api/v1/auth)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/v1/auth/register |
Register new user | ❌ |
POST |
/connect/token |
OAuth2 token endpoint (login, refresh) | ❌ |
POST |
/api/v1/auth/change-password |
Change password | ✅ |
POST |
/api/v1/auth/logout |
Logout (revoke tokens) | ✅ |
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 delete) | ✅ |
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"
}'
Response:
{
"success": true,
"data": {
"userId": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com"
}
}
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 "username=user@example.com" \
-d "password=Password123!" \
-d "scope=openid profile email offline_access"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"scope": "openid profile email offline_access"
}
Step 3: Use Access Token
Use the access_token in Authorization header for protected APIs:
curl http://localhost:5001/api/v1/users/me \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9..."
Step 4: Refresh Token (When Access Token Expires)
curl -X POST http://localhost:5001/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9..."
Step 5: Logout
curl -X POST http://localhost:5001/api/v1/auth/logout \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Client Credentials (Service-to-Service)
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"
Health Checks
| Endpoint | Purpose |
|---|---|
/health |
Full health status |
/health/live |
Liveness probe |
/health/ready |
Readiness probe |
Swagger UI
After running the service, access Swagger UI at:
- Local: http://localhost:5001/swagger
- Docker: http://localhost/api/v1/iam/swagger
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/
├── Dockerfile
└── docker-compose.yml
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Environment | Development |
DATABASE_URL |
PostgreSQL connection | - |
JWT_SECRET |
JWT signing secret (32+ chars) | - |
REDIS_URL |
Redis connection | - |
Password Policy
- Minimum 8 characters
- Requires uppercase, lowercase, digit, special character
Token Lifetimes
| Token | Lifetime |
|---|---|
| Access Token | 15 minutes |
| Refresh Token | 7 days |
Development
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run tests
dotnet test
# Run API
dotnet run --project src/IamService.API
Docker
# Build image
docker build -t iam-service:latest .
# Run container
docker run -p 5001:8080 --env-file .env iam-service:latest
Resources
License
Proprietary - GoodGo Platform