Serviço de autenticação e gerenciamento de usuários para o Location404 - sistema completo de registro, login, tokens JWT e perfis de usuário.
- Sobre o Projeto
- Funcionalidades
- Arquitetura
- Tecnologias
- Pré-requisitos
- Instalação
- Configuração
- Como Usar
- API Endpoints
- Estrutura do Projeto
- Testes
- Observabilidade
- Licença
O Location404 Auth Service é o serviço de autenticação centralizado do Location404. Utilizando JWT (JSON Web Tokens) e BCrypt para hashing de senhas, o serviço fornece:
- Registro de Usuários: Criação de contas com email/senha
- Autenticação: Login seguro com JWT
- Refresh Tokens: Renovação automática de tokens
- Gestão de Perfil: Atualização de informações e avatar
- Segurança: BCrypt para senhas, HttpOnly cookies, CORS configurável
- Usuário se registra → Senha hasheada com BCrypt, usuário criado no PostgreSQL
- Login realizado → Validação de credenciais, geração de JWT + Refresh Token
- Tokens retornados → Access Token (15min) e Refresh Token (7 dias) em HttpOnly cookies
- Requisições autenticadas → Frontend envia cookies automaticamente
- Token expirado → Frontend chama
/refreshpara renovar tokens - Perfil atualizado → Upload de imagem (validação de tipo e tamanho)
- ✅ Login com email e senha
- ✅ JWT com expiração configurável (padrão: 60min)
- ✅ Refresh Tokens com rotação automática (padrão: 24h)
- ✅ HttpOnly cookies para segurança
- ✅ Logout (invalidação de refresh token)
- ✅ Registro com validação de email único
- ✅ Atualização de perfil (username, bio)
- ✅ Upload de imagem de perfil
- ✅ Validação de tipos de arquivo (JPEG, PNG, GIF)
- ✅ Limite de tamanho de imagem (5MB)
- ✅ Hashing de senhas com BCrypt (work factor 10)
- ✅ Validação de força de senha (min 6 caracteres)
- ✅ Tokens armazenados em HttpOnly cookies (proteção XSS)
- ✅ CORS configurável por ambiente
- ✅ Rate limiting (opcional, via middleware)
- ✅ OpenTelemetry integrado
- ✅ Traces distribuídos (login, registro, refresh)
- ✅ Métricas customizadas (usuários criados, logins, falhas)
- ✅ Logs estruturados com trace correlation
O projeto segue Clean Architecture com separação clara de responsabilidades:
┌─────────────────────────────────────────────────────────────┐
│ API Layer (Minimal APIs) │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Auth │ │ User │ │ Filters & │ │
│ │ Endpoints │ │ Endpoints │ │ Middlewares │ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Commands/Queries │ │ Interfaces & Validators │ │
│ │ - CreateUser │ │ - IUserRepository │ │
│ │ - Authenticate │ │ - IPasswordHasher │ │
│ │ - RefreshToken │ │ - ITokenGenerator │ │
│ └──────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Domain Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Entities │ │ Value Objects│ │ Domain Events │ │
│ │ - User │ │ - Email │ │ - UserCreated │ │
│ │ - RefreshTkn│ │ - Password │ │ - UserAuth'd │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │
│ │PostgreSQL│ │ BCrypt │ │ JWT │ │File Storage │ │
│ │ (EF Core)│ │(Hashing)│ │ (Tokens) │ │ (Images) │ │
│ └──────────┘ └─────────┘ └──────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Endpoints: AuthenticationEndpoints, UserManagementEndpoints
- Filters: ValidationFilter (FluentValidation)
- Middlewares: CORS, Authentication, Exception Handling
- Commands: CreateUser, Login, RefreshToken, UpdateUser
- Queries: GetCurrentUserInformation
- Validators: FluentValidation rules
- Interfaces: IPasswordHasher, ITokenGenerator
- Entities: User (aggregate root)
- Value Objects: Email, Password, ProfileImage
- Domain Events: UserCreated, UserAuthenticated
- Persistence: EF Core + PostgreSQL
- Security: BCrypt.Net para hashing
- JWT: System.IdentityModel.Tokens.Jwt
- File Storage: Local filesystem (profile images)
Frontend
│
├─ POST /api/users ──────► CreateUserCommand
│ │
│ ├─ Validate email unique
│ ├─ Hash password (BCrypt)
│ ├─ Store in PostgreSQL
│ │
├─◄ 201 Created User created
│
├─ POST /api/auth/login ──► AuthenticateCommand
│ │
│ ├─ Find user by email
│ ├─ Verify password (BCrypt.Verify)
│ ├─ Generate JWT (claims: sub, email, username)
│ ├─ Generate Refresh Token (GUID)
│ ├─ Store Refresh Token in DB
│ │
├─◄ 200 OK Set HttpOnly Cookies:
│ (+ cookies) - accessToken (15min)
│ - refreshToken (7 days)
│
├─ GET /api/users/me ─────► GetCurrentUserQuery
│ (Authorization: JWT) │
│ ├─ Extract userId from JWT claims
│ ├─ Fetch user from PostgreSQL
│ │
├─◄ 200 OK User information
│
├─ POST /api/auth/refresh ► RefreshTokenCommand
│ (refreshToken cookie) │
│ ├─ Validate refresh token
│ ├─ Check expiration
│ ├─ Generate new JWT
│ ├─ Rotate refresh token (optional)
│ │
├─◄ 200 OK New tokens in cookies
- .NET 9.0 - Framework principal
- ASP.NET Core Minimal APIs - Endpoints RESTful
- LiteBus - CQRS pattern (Command/Query handlers)
- FluentValidation - Validação de requests
- PostgreSQL 16 - Banco de dados relacional
- Entity Framework Core 9 - ORM
- Npgsql - Provider PostgreSQL
- BCrypt.Net - Password hashing
- System.IdentityModel.Tokens.Jwt - JWT generation/validation
- Microsoft.AspNetCore.Authentication.JwtBearer - JWT middleware
- OpenTelemetry - Distributed tracing
- Shared.Observability - Pacote NuGet customizado
- Prometheus - Métricas
- Grafana Loki - Logs estruturados
- xUnit - Framework de testes
- FluentAssertions - Assertions expressivas
- Moq - Mocking
- EF Core InMemory - Testes de repositório
Opcional:
- Docker - Para rodar PostgreSQL via containers
git clone https://github.com/Location404/location404-auth.git
cd location404-authdotnet restoredotnet buildcd src/Location404.Auth.API
dotnet ef database update --project ../Location404.Auth.InfrastructureEdite src/Location404.Auth.API/appsettings.json ou use variáveis de ambiente:
{
"ConnectionStrings": {
"UserIdentityDatabaseProduction": "Host=localhost;Port=5432;Database=location404_auth;Username=postgres;Password=your_password"
},
"JwtSettings": {
"Issuer": "location404",
"Audience": "location404",
"SigningKey": "your-super-secret-key-min-32-chars-here",
"AccessTokenMinutes": 60,
"RefreshTokenMinutes": 1440
},
"Cors": {
"AllowedOrigins": [
"http://localhost:5173",
"http://localhost:4200"
]
}
}# Database
ConnectionStrings__UserIdentityDatabaseProduction=Host=postgres;Port=5432;Database=location404_auth;Username=location404;Password=secure_password
# JWT
JwtSettings__SigningKey=your-super-secret-signing-key-min-32-characters
JwtSettings__AccessTokenMinutes=60
JwtSettings__RefreshTokenMinutes=1440
# CORS
Cors__AllowedOrigins__0=https://location404.com
# OpenTelemetry
OpenTelemetry__CollectorEndpoint=http://otel-collector:4317
OpenTelemetry__Tracing__SamplingRatio=0.1# Linux/macOS
openssl rand -base64 32
# Windows (PowerShell)
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 }))# 1. Inicie o PostgreSQL (ou use Docker)
docker run -d \
--name postgres-auth \
-e POSTGRES_DB=location404_auth \
-e POSTGRES_USER=location404 \
-e POSTGRES_PASSWORD=dev_password \
-p 5432:5432 \
postgres:16-alpine
# 2. Aplique as migrations
cd src/Location404.Auth.API
dotnet ef database update --project ../Location404.Auth.Infrastructure
# 3. Execute o serviço
dotnet runA API estará disponível em:
- Base URL:
http://localhost:5185 - Swagger/Scalar:
http://localhost:5185/scalar/v1 - Health Check:
http://localhost:5185/health - Metrics:
http://localhost:5185/metrics
cd location404-utils/deploy/dev
docker-compose up -d location404-auth postgresAutentica um usuário com email e senha.
Request:
{
"email": "user@example.com",
"password": "SecurePassword123"
}Response (200 OK):
{
"userId": "guid-here",
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "guid-refresh-token",
"expiresIn": 3600
}Cookies Set:
accessToken- HttpOnly, Expires: 15minrefreshToken- HttpOnly, Expires: 7 days, Path:/api/auth/refresh
Status Codes:
200 OK- Login bem-sucedido401 Unauthorized- Credenciais inválidas
Renova o access token usando o refresh token.
Request:
- Requer cookie
refreshToken
Response (200 OK):
{
"accessToken": "new-jwt-token",
"expiresIn": 3600
}Status Codes:
200 OK- Token renovado com sucesso401 Unauthorized- Refresh token inválido ou expirado
Cria um novo usuário.
Request:
{
"username": "johndoe",
"email": "john@example.com",
"password": "SecurePass123"
}Response (201 Created):
{
"id": "guid-here",
"username": "johndoe",
"email": "john@example.com",
"createdAt": "2025-11-25T12:00:00Z"
}Validations:
- Email único (não pode duplicar)
- Senha mínima: 6 caracteres
- Username: 3-20 caracteres, alfanumérico
Status Codes:
201 Created- Usuário criado com sucesso400 Bad Request- Validação falhou409 Conflict- Email já cadastrado
Retorna informações do usuário autenticado.
Headers:
Authorization: Bearer {jwt-token}
Response (200 OK):
{
"id": "guid-here",
"username": "johndoe",
"email": "john@example.com",
"profileImageUrl": "https://cdn.location404.com/profiles/guid.jpg",
"bio": "Passionate gamer and traveler",
"createdAt": "2025-11-25T12:00:00Z"
}Status Codes:
200 OK- Usuário encontrado401 Unauthorized- Token inválido ou ausente404 Not Found- Usuário não encontrado
Atualiza informações do usuário.
Headers:
Authorization: Bearer {jwt-token}
Content-Type: multipart/form-data
Request (multipart/form-data):
username: newusername
bio: Updated bio text
profileImage: [binary file data]
Validations:
- ProfileImage: Max 5MB
- Tipos aceitos: JPEG, PNG, GIF
- Username: 3-20 caracteres
Response (204 No Content)
Status Codes:
204 No Content- Atualização bem-sucedida400 Bad Request- Validação falhou (arquivo muito grande, tipo inválido)401 Unauthorized- Token inválido403 Forbidden- Tentativa de atualizar outro usuário404 Not Found- Usuário não encontrado
Cria usuário via autenticação externa (Google, etc).
Request:
{
"provider": "Google",
"providerUserId": "google-id-123",
"email": "user@gmail.com",
"username": "johndoe",
"profileImageUrl": "https://lh3.googleusercontent.com/..."
}Response (201 Created):
{
"id": "guid-here",
"username": "johndoe",
"email": "user@gmail.com",
"provider": "Google"
}Status Codes:
201 Created- Usuário criado400 Bad Request- Provider inválido409 Conflict- Email já cadastrado
Para endpoints protegidos, inclua o JWT no header:
curl -H "Authorization: Bearer {your-jwt-token}" \
http://localhost:5185/api/users/meOu configure o cliente HTTP para enviar cookies automaticamente:
// Axios
axios.defaults.withCredentials = true;
// Fetch
fetch('/api/users/me', {
credentials: 'include'
});location404-auth/
├── src/
│ ├── Location404.Auth.API/ # API Layer (Minimal APIs)
│ │ ├── Endpoints/
│ │ │ ├── AuthenticationEndpoints.cs # Login, Refresh
│ │ │ └── UserManagementEndpoints.cs # User CRUD
│ │ ├── Filters/
│ │ │ └── ValidationFilter.cs # FluentValidation filter
│ │ ├── Program.cs # Entry point + DI setup
│ │ └── appsettings.json
│ │
│ ├── Location404.Auth.Application/ # Application Layer (CQRS)
│ │ ├── Features/
│ │ │ ├── Authentication/
│ │ │ │ └── Commands/
│ │ │ │ ├── AuthenticateUserWithPasswordCommand/
│ │ │ │ └── RefreshTokenCommand/
│ │ │ │
│ │ │ └── UserManagement/
│ │ │ ├── Commands/
│ │ │ │ ├── CreateUserWithPasswordCommand/
│ │ │ │ ├── CreateUserWithExternalProviderCommand/
│ │ │ │ └── UpdateUserInformationsCommand/
│ │ │ └── Queries/
│ │ │ └── GetCurrentUserInformation/
│ │ │
│ │ ├── Common/
│ │ │ ├── Result/ # Result pattern
│ │ │ └── Interfaces/
│ │ │ ├── IPasswordHasher.cs
│ │ │ ├── ITokenGenerator.cs
│ │ │ └── IUserRepository.cs
│ │ │
│ │ └── DTOs/
│ │ ├── UserDto.cs
│ │ └── TokenDto.cs
│ │
│ ├── Location404.Auth.Domain/ # Domain Layer
│ │ ├── Entities/
│ │ │ ├── User.cs # Aggregate root
│ │ │ └── RefreshToken.cs
│ │ │
│ │ └── ValueObjects/
│ │ ├── Email.cs
│ │ ├── Password.cs
│ │ └── ProfileImage.cs
│ │
│ └── Location404.Auth.Infrastructure/ # Infrastructure
│ ├── Persistence/
│ │ ├── ApplicationDbContext.cs # EF Core DbContext
│ │ ├── Configurations/
│ │ │ └── UserConfiguration.cs # Entity configuration
│ │ └── Repositories/
│ │ └── UserRepository.cs # Repository implementation
│ │
│ ├── Security/
│ │ ├── BcryptPasswordHasher.cs # BCrypt implementation
│ │ └── JwtTokenGenerator.cs # JWT generation
│ │
│ ├── FileStorage/
│ │ └── LocalFileStorageService.cs # Profile image storage
│ │
│ ├── Migrations/ # EF Core migrations
│ │ ├── 20250101000000_InitialCreate.cs
│ │ └── ApplicationDbContextModelSnapshot.cs
│ │
│ └── DependencyInjection.cs
│
├── tests/
│ ├── Location404.Auth.Application.UnitTests/
│ │ ├── Commands/
│ │ │ ├── AuthenticateUserTests.cs
│ │ │ ├── CreateUserTests.cs
│ │ │ └── RefreshTokenTests.cs
│ │ └── Validators/
│ │ └── CreateUserValidatorTests.cs
│ │
│ └── Location404.Auth.Domain.UnitTests/
│ ├── Entities/
│ │ └── UserTests.cs
│ └── ValueObjects/
│ ├── EmailTests.cs
│ └── PasswordTests.cs
│
├── Location404.Auth.sln
├── README.md
└── .gitignore
dotnet testdotnet test tests/Location404.Auth.Domain.UnitTestsdotnet test tests/Location404.Auth.Application.UnitTestsdotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"TestResults/Report"Abra TestResults/Report/index.html no navegador.
Endpoint: http://localhost:5185/metrics
Métricas customizadas:
auth_users_created_total- Total de usuários criadosauth_logins_total- Total de logins (success/failure)auth_token_refreshes_total- Total de refresh tokensauth_password_hash_duration_seconds- Tempo de hashing BCrypt
Configurado para exportar para coletor OTLP:
- Endpoint:
http://181.215.135.221:4317 - Sampling: 10% em produção, 100% em desenvolvimento
Traces automáticos:
- HTTP requests (inbound)
- Database queries (EF Core)
- External API calls
- Command/Query handlers
Logs estruturados exportados para Grafana Loki:
- Formato: JSON
- Trace correlation:
trace_id,span_id - Enriched com properties:
user_id,email,command_name
# Health geral
curl http://localhost:5185/health
# Readiness (dependências prontas?)
curl http://localhost:5185/health/ready
# Liveness (processo vivo?)
curl http://localhost:5185/health/liveDependências verificadas:
- PostgreSQL (timeout: 5s)
- File system (writable, para uploads)
Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.
- location404-web - Frontend Vue.js
- location404-game - Game engine SignalR
- location404-data - API de dados e estatísticas
- shared-observability - Pacote de observabilidade
- Issues: GitHub Issues
- Discussões: GitHub Discussions
Desenvolvido por ryanbromati