MATIH Platform is in active MVP development. Documentation reflects current implementation status.
6. Identity & Access Management
Authentication
Email Verification

Email Verification

Production - POST /api/v1/auth/verify-email, POST /api/v1/auth/resend-verification

Email verification confirms that a user owns the email address they registered with. A verification code is sent via the Notification Service and must be submitted to complete verification.


6.2.7Verification Flow

User registers  -->  Verification email sent  -->  User submits code  -->  Email verified
                     (6-digit code, 30min TTL)     POST /verify-email      emailVerified=true

Verify Email

curl -X POST http://localhost:8081/api/v1/auth/verify-email \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "code": "482917"
  }'

Request Schema

FieldTypeRequiredDescription
emailStringYesThe email address to verify
codeStringYesThe verification code from the email

Response (200 OK)

Empty response body on success.


6.2.8Resend Verification

If the user did not receive the verification email or the code expired, they can request a new one:

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

Rate Limiting

Resend requests are rate-limited to one per minute per user. Attempting to resend before the cooldown period returns an error.


6.2.9Implementation Details

Token Creation

When a verification email is sent, an EmailVerificationToken entity is created:

public void sendVerificationEmail(User user) {
    // Invalidate any existing tokens
    emailVerificationTokenRepository.invalidateAllForUser(user.getId(), Instant.now());
 
    // Create new verification token (30-minute validity)
    EmailVerificationToken token = EmailVerificationToken.createForUser(
        user, EMAIL_VERIFICATION_VALIDITY_MINUTES);
    emailVerificationTokenRepository.save(token);
 
    // Send via Notification Service
    notificationServiceClient.sendEmailWithTemplate(
        user.getTenantId(),
        null,
        user.getEmail(),
        "email-verification",
        Map.of(
            "userName", user.getFirstName() != null ? user.getFirstName() : "User",
            "verificationCode", token.getToken(),
            "validityMinutes", EMAIL_VERIFICATION_VALIDITY_MINUTES,
            "verificationUrl", "https://app.matih.ai/verify-email?code=" + token.getToken()
        )
    );
}

Verification Logic

@Transactional
public void verifyEmail(VerifyEmailRequest request) {
    User user = userRepository.findByEmail(request.getEmail())
        .orElseThrow(() -> new BusinessException("User not found"));
 
    if (user.isEmailVerified()) {
        return;  // Already verified - idempotent
    }
 
    EmailVerificationToken token = emailVerificationTokenRepository
        .findValidTokenByCodeAndEmail(request.getCode(), request.getEmail(), Instant.now())
        .orElseThrow(() -> new BusinessException("Invalid or expired verification code"));
 
    if (token.tooManyAttempts()) {
        throw new BusinessException(
            "Too many failed attempts. Please request a new verification code.");
    }
 
    if (!token.getToken().equals(request.getCode())) {
        token.incrementAttempts();
        emailVerificationTokenRepository.save(token);
        throw new BusinessException("Invalid verification code");
    }
 
    // Mark token as used
    token.markAsUsed();
    emailVerificationTokenRepository.save(token);
 
    // Mark user email as verified
    user.setEmailVerified(true);
    userRepository.save(user);
}

Key Behaviors

  • Idempotent: Verifying an already-verified email is a no-op (returns success)
  • Attempt Tracking: Failed verification attempts are counted. After too many failures, the token is invalidated
  • Token Invalidation: When a new verification is requested, all existing tokens for the user are invalidated
  • Fallback Logging: If the Notification Service is unavailable, the verification code is logged for development environments

Error Codes

CodeHTTP StatusDescription
BUSINESS_RULE_VIOLATION400Invalid/expired code, email already verified, rate limited
RESOURCE_NOT_FOUND404User not found