Task 18: Create Authentication API Endpoint
Role
BackendOverview
Implement the core authentication API endpoint that accepts user credentials, validates them against the database, and returns JWT tokens for authenticated sessions.
Objectives
- Create secure REST API endpoint for user authentication
- Implement credential validation logic
- Generate and return JWT access/refresh tokens
- Handle authentication errors gracefully
- Log authentication attempts for security monitoring
Technical Requirements
API Endpoint Specification
Endpoint: POST /api/v1/auth/login
Request Headers:
Content-Type: application/json
User-Agent: <client-info>
X-Request-ID: <uuid>
Request Body:
{
"email": "client01@micdots.com",
"password": "Mic1234!"
}
Success Response (200 OK):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "client01@micdots.com",
"name": "Client 01",
"role": "user",
"createdAt": "2024-01-15T10:30:00Z"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
}
}
Error Responses:
// 400 Bad Request - Invalid input
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Email and password are required",
"fields": ["email", "password"]
}
}
// 401 Unauthorized - Invalid credentials
{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid email or password"
}
}
// 429 Too Many Requests - Rate limit exceeded
{
"success": false,
"error": {
"code": "TOO_MANY_ATTEMPTS",
"message": "Too many login attempts. Please try again in 15 minutes.",
"retryAfter": 900
}
}
// 500 Internal Server Error
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later."
}
}
Implementation (C# / ASP.NET Core)
Controller Implementation
[ApiController]
[Route("api/v1/auth")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
private readonly ILogger<AuthController> _logger;
public AuthController(IAuthService authService, ILogger<AuthController> logger)
{
_authService = authService;
_logger = logger;
}
[HttpPost("login")]
[ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status429TooManyRequests)]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
try
{
// Validate input
if (string.IsNullOrEmpty(request.Email) || string.IsNullOrEmpty(request.Password))
{
return BadRequest(new ErrorResponse
{
Success = false,
Error = new ErrorDetail
{
Code = "VALIDATION_ERROR",
Message = "Email and password are required"
}
});
}
// Authenticate user
var result = await _authService.AuthenticateAsync(request.Email, request.Password);
if (!result.Success)
{
_logger.LogWarning("Failed login attempt for email: {Email}", request.Email);
return Unauthorized(new ErrorResponse
{
Success = false,
Error = new ErrorDetail
{
Code = "INVALID_CREDENTIALS",
Message = "Invalid email or password"
}
});
}
_logger.LogInformation("Successful login for user: {UserId}", result.User.Id);
return Ok(new LoginResponse
{
Success = true,
Data = new AuthData
{
User = result.User,
AccessToken = result.AccessToken,
RefreshToken = result.RefreshToken,
ExpiresIn = 3600
}
});
}
catch (RateLimitExceededException ex)
{
return StatusCode(StatusCodes.Status429TooManyRequests, new ErrorResponse
{
Success = false,
Error = new ErrorDetail
{
Code = "TOO_MANY_ATTEMPTS",
Message = ex.Message,
RetryAfter = ex.RetryAfter
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error during login");
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse
{
Success = false,
Error = new ErrorDetail
{
Code = "INTERNAL_ERROR",
Message = "An unexpected error occurred. Please try again later."
}
});
}
}
}
Service Implementation
public interface IAuthService
{
Task<AuthResult> AuthenticateAsync(string email, string password);
}
public class AuthService : IAuthService
{
private readonly IUserRepository _userRepository;
private readonly IPasswordHasher _passwordHasher;
private readonly IJwtTokenGenerator _tokenGenerator;
public AuthService(
IUserRepository userRepository,
IPasswordHasher passwordHasher,
IJwtTokenGenerator tokenGenerator)
{
_userRepository = userRepository;
_passwordHasher = passwordHasher;
_tokenGenerator = tokenGenerator;
}
public async Task<AuthResult> AuthenticateAsync(string email, string password)
{
// Find user by email
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
{
return AuthResult.Failure("Invalid credentials");
}
// Verify password
bool isPasswordValid = _passwordHasher.Verify(password, user.PasswordHash);
if (!isPasswordValid)
{
return AuthResult.Failure("Invalid credentials");
}
// Generate tokens
var accessToken = _tokenGenerator.GenerateAccessToken(user);
var refreshToken = _tokenGenerator.GenerateRefreshToken(user);
// Save refresh token
await _userRepository.SaveRefreshTokenAsync(user.Id, refreshToken);
return AuthResult.Success(user, accessToken, refreshToken);
}
}
Security Considerations
- ⚠️ Never return specific error messages that reveal whether email or password was wrong
- ⚠️ Use constant-time comparison for password verification to prevent timing attacks
- ⚠️ Log all failed authentication attempts for security monitoring
- ⚠️ Implement rate limiting to prevent brute force attacks
- ⚠️ Use HTTPS only for all authentication endpoints
- ⚠️ Sanitize and validate all input data
- ⚠️ Never log passwords or tokens
Database Schema
CREATE TABLE Users (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
Email NVARCHAR(254) NOT NULL UNIQUE,
PasswordHash NVARCHAR(255) NOT NULL,
Name NVARCHAR(100) NOT NULL,
Role NVARCHAR(50) NOT NULL DEFAULT 'user',
IsActive BIT NOT NULL DEFAULT 1,
FailedLoginAttempts INT NOT NULL DEFAULT 0,
LockedUntil DATETIME2 NULL,
CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
UpdatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE()
);
CREATE INDEX IX_Users_Email ON Users(Email);
CREATE TABLE RefreshTokens (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
UserId UNIQUEIDENTIFIER NOT NULL,
Token NVARCHAR(500) NOT NULL,
ExpiresAt DATETIME2 NOT NULL,
CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
RevokedAt DATETIME2 NULL,
FOREIGN KEY (UserId) REFERENCES Users(Id) ON DELETE CASCADE
);
CREATE INDEX IX_RefreshTokens_UserId ON RefreshTokens(UserId);
CREATE INDEX IX_RefreshTokens_Token ON RefreshTokens(Token);
Acceptance Criteria
- Endpoint accepts POST requests at
/api/v1/auth/login - Validates email and password are provided
- Returns 400 for invalid input
- Returns 401 for invalid credentials
- Returns 200 with tokens for valid credentials
- Generates valid JWT access token (1 hour expiry)
- Generates valid JWT refresh token (7 days expiry)
- Returns user object with id, email, name, role
- Logs all authentication attempts
- Handles database errors gracefully
- Returns appropriate error codes
- Error messages don't reveal security details
- Rate limiting is enforced
- CORS headers are configured correctly
- API documentation is updated
Testing Checklist
Unit Tests
- Test successful authentication
- Test invalid email format
- Test missing password
- Test wrong password
- Test non-existent user
- Test locked account
- Test token generation
- Test error handling
Integration Tests
- Test end-to-end login flow
- Test with real database
- Test rate limiting
- Test concurrent requests
- Test token validation
Estimated Time
2 days (16 hours)
Dependencies
- Task 19: Implement JWT token generation
- Task 20: Add password hashing/verification
- Task 21: Implement rate limiting
- Database schema must be created
Related Content
Related Tasks
- Task 19: Implement JWT Token Generation (Coming Soon)
- Task 20: Add Password Hashing/Verification (Coming Soon)
- Task 21: Implement Rate Limiting
- Task 22: Add Login Attempt Tracking (Coming Soon)
- Task 23: Create Session Management (Coming Soon)