640 lines
18 KiB
Markdown
640 lines
18 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
# Edit DATABASE_URL, JWT_SECRET in .env
|
|
```
|
|
|
|
### 3. Run with Docker
|
|
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
Service available at: `http://localhost:5001`
|
|
|
|
### 4. Run Locally
|
|
|
|
```bash
|
|
dotnet restore
|
|
dotnet build
|
|
dotnet run --project src/IamService.API
|
|
```
|
|
|
|
## Database Migrations
|
|
|
|
### Prerequisites
|
|
|
|
```bash
|
|
# Install EF Core tools (one-time)
|
|
dotnet tool install --global dotnet-ef
|
|
```
|
|
|
|
### Create Migration
|
|
|
|
```bash
|
|
dotnet ef migrations add <MigrationName> \
|
|
--project src/IamService.Infrastructure \
|
|
--startup-project src/IamService.API
|
|
```
|
|
|
|
### Apply Migration
|
|
|
|
```bash
|
|
dotnet ef database update \
|
|
--project src/IamService.Infrastructure \
|
|
--startup-project src/IamService.API
|
|
```
|
|
|
|
### Neon Database Setup
|
|
|
|
1. Create database on [Neon Console](https://console.neon.tech)
|
|
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
|
|
|
|
```bash
|
|
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:**
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```bash
|
|
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:**
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjlGMzk1RDQzMDIz...",
|
|
"token_type": "Bearer",
|
|
"expires_in": 900,
|
|
"refresh_token": "5B8FB8189575241FF63C32ED2D0D492B32DA533B...",
|
|
"scope": "api email offline_access openid profile"
|
|
}
|
|
```
|
|
|
|
### Step 3: Use Access Token
|
|
|
|
```bash
|
|
curl http://localhost:5001/api/v1/users/me \
|
|
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
|
|
```
|
|
|
|
### Step 4: Refresh Token
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
curl -X POST http://localhost:5001/api/v1/auth/send-verification-email \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "user@example.com"}'
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"success": true,
|
|
"message": "Verification email sent successfully."
|
|
},
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
```
|
|
|
|
### Confirm Email
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
curl -X POST http://localhost:5001/api/v1/auth/2fa/enable \
|
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
curl http://localhost:5001/api/v1/auth/linked-accounts \
|
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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`:
|
|
|
|
```json
|
|
{
|
|
"Redis": {
|
|
"Host": "localhost",
|
|
"Port": 6379,
|
|
"Password": "",
|
|
"Database": 0,
|
|
"ConnectTimeout": 5000,
|
|
"SyncTimeout": 5000
|
|
}
|
|
}
|
|
```
|
|
|
|
Or use environment variables:
|
|
|
|
```bash
|
|
REDIS_HOST=your-redis-host
|
|
REDIS_PORT=6379
|
|
REDIS_PASSWORD=your-password
|
|
REDIS_DATABASE=0
|
|
```
|
|
|
|
### ICacheService Interface
|
|
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
// 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):**
|
|
```csharp
|
|
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):**
|
|
```csharp
|
|
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
|
|
|
|
```bash
|
|
# Build image
|
|
docker build -t goodgo/iam-service:latest .
|
|
|
|
# Run container
|
|
docker run -p 5001:8080 --env-file .env goodgo/iam-service:latest
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [Duende IdentityServer Documentation](https://docs.duendesoftware.com/identityserver/v7/)
|
|
- [ASP.NET Core Identity](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity)
|
|
- [OAuth2 Specification](https://oauth.net/2/)
|
|
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)
|
|
|
|
## License
|
|
|
|
Proprietary - GoodGo Platform
|