Skip to content

Latest commit

 

History

History
386 lines (279 loc) · 9.62 KB

File metadata and controls

386 lines (279 loc) · 9.62 KB

User Service

A Spring Boot microservice for managing user data synchronized from AWS Cognito authentication.

Overview

This service provides user management capabilities for applications using AWS Cognito for authentication. It automatically creates and maintains user records based on JWT tokens issued by Cognito, using the Cognito user subject as the primary key.

Key Features

  • Cognito Integration: Verifies JWT tokens using AWS Cognito's JWKS endpoint (RS256 signature verification)
  • Automatic User Sync: Creates user records on first sign-in, returns existing users on subsequent requests
  • Idempotent Operations: Safe to call repeatedly with the same token
  • Subject-based Identity: Uses Cognito subject claim as the stable user ID
  • No Password Storage: Authentication is fully delegated to AWS Cognito
  • Logout Support: Revokes Refresh Tokens via Cognito GlobalSignOut on POST /user/logout

Architecture

Flutter App → AWS Cognito (Sign-in/Sign-up)
                ↓ (JWT tokens)
Flutter App → User Service → Database

Authentication Flow

  1. User signs up/in through AWS Cognito (handled by your Flutter app)
  2. Cognito returns access_token and id_token
  3. Flutter app calls POST /api/users/create-user with tokens
  4. Service verifies token signature, extracts user info
  5. Service creates new user (first-time) or returns existing user
  6. Flutter app uses the returned userid for subsequent API calls

API Endpoints

Create/Sync User

Create a new user on first sign-in or return existing user.

POST /api/users/create-user
Authorization: Bearer {cognito_access_token}
X-Id-Token: {cognito_id_token}  (optional)

Aliases: /api/users/sync, /api/users/me

Success Response (200):

{
  "userid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "user@example.com",
  "preferenceId": null
}

Error Response (400):

{
  "error": "Token verification failed: JWT signature verification failed"
}

Get User by ID

Retrieve a user by their Cognito subject ID.

GET /api/users/{userid}

Success Response (200):

{
  "userid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "user@example.com",
  "preferenceId": null
}

Error Response (404): User not found

Delete User by ID

Delete a user by their Cognito subject ID.

Delete /api/users/{userid}

Success Response (204):

No Content

Error Response (404): User not found

Logout

Revoke all Refresh Tokens for the authenticated user via AWS Cognito GlobalSignOut.

POST /user/logout
Authorization: Bearer {cognito_access_token}

Success Response (204): No Content

Error Responses:

  • 400 Bad Request — Missing or malformed Authorization header
  • 401 Unauthorized — Token is invalid or already expired

Important note for frontend: Upon receiving 204, the client must delete all tokens (Access Token, ID Token, Refresh Token) from local storage. The Access Token may remain technically valid for up to 1 hour after logout — this is an accepted limitation of stateless JWT. No new tokens can be issued using the revoked Refresh Token.

Configuration

Required Settings

Update src/main/resources/application.properties:

# AWS Cognito Configuration
aws.cognito.region=ap-southeast-1
aws.cognito.userPoolId=ap-southeast-1_HxnNvwF6v
aws.cognito.jwks.url=https://cognito-idp.${aws.cognito.region}.amazonaws.com/${aws.cognito.userPoolId}/.well-known/jwks.json

Finding Your Cognito Details

  1. User Pool ID: AWS Console → Cognito → User Pools → [Your Pool] → Pool ID at top
  2. Region: The AWS region where your User Pool exists (e.g., ap-southeast-1)
  3. Client ID: Found under App Integration → App Clients (needed for sign-in, not this service)

Technology Stack

  • Java 21
  • Spring Boot 3.3.4
  • Spring Data JPA - Database access
  • Nimbus JOSE+JWT - JWT signature verification
  • Gradle - Build tool

Local Development

Prerequisites

  • Java 21+
  • Gradle (or use included wrapper)

Running User Service Locally using AWS SSM port forwarding

Architecture Overview

Local Machine
   ↓
localhost:5432
   ↓
SSM Port Forwarding
   ↓
Bastion EC2 (inside VPC)
   ↓
RDS (private)

1. Install AWS CLI

macOS (Homebrew)

brew install awscli

Verify installation

aws --version

2. Install Session Manager Plugin

brew install session-manager-plugin

Verify installation

session-manager-plugin

3. Configure AWS Credentials

aws configure

Enter:

AWS Access Key ID: <aws-access-key>
AWS Secret Access Key: <aws-secret-key>
Default region: ap-southeast-1
Default output format: json

4. Verify Identity

aws sts get-caller-identity

Expected

"Arn": "arn:aws:iam::<account-id>:user/SWE5006"

5. Start SSM Tunnel

Keep this terminal open. It maintains the tunnel.

aws ssm start-session \
  --target i-061983d3385eb80db \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["swe5006-nus-g3-pg-dev.clee6i664xzo.ap-southeast-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'

Expected Output

Starting session with SessionId: ...
Port 5432 opened...

6. Run Application

Option A — IntelliJ

  1. Go to Run → Edit Configurations
  2. Set Environment variables:
SPRING_PROFILES_ACTIVE=local

Option B — CLI

SPRING_PROFILES_ACTIVE=local ./gradlew bootRun

7. Get Tokens from Cognito

curl -X POST 'https://cognito-idp.ap-southeast-1.amazonaws.com/' \
  --header 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
  --header 'Content-Type: application/x-amz-json-1.1' \
  --data '{
    "AuthFlow": "USER_PASSWORD_AUTH",
    "ClientId": "1d1jkchdvgt5tldbb0hivruird",
    "AuthParameters": {
      "USERNAME": "your-email@example.com",
      "PASSWORD": "your-password"
    }
  }'

8. Create/Sync User

ACCESS_TOKEN="<paste_access_token_here>"

curl -X POST 'http://localhost:8080/api/users/create-user' \
  --header "Authorization: Bearer $ACCESS_TOKEN"

9. Get User

USER_ID="<userid_from_previous_response>"

curl -X GET "http://localhost:8080/api/users/$USER_ID"

Testing

Unit Tests

Run the standard test suite with coverage:

./gradlew clean test jacocoTestReport

Reports:

  • Test report: build/reports/tests/test/index.html
  • Coverage report: build/reports/jacoco/test/html/index.html

Integration Tests

Integration tests live under src/integrationTest and run with a dedicated Gradle task:

./gradlew integrationTest

If you are running against a local PostgreSQL instance, make sure the database is reachable first. The test profile reads:

  • DB_HOST
  • DB_PORT
  • DB_NAME
  • DB_USERNAME
  • DB_PASSWORD

with local defaults defined in src/integrationTest/resources/application-test.yaml.

Report:

  • Integration test report: build/reports/tests/integrationTest/index.html

Performance Tests

Performance tests remain in the separate Gradle subproject performancetest and are run with Gatling:

./gradlew :performancetest:gatlingRun

Report:

  • Gatling report: performancetest/build/reports/gatling/<simulation-run>/index.html

Database Schema

app_user Table

Column Type Description
userid VARCHAR(255) Primary key - Cognito subject claim
email VARCHAR(255) User's email from JWT claims
preference_id VARCHAR(255) Optional user preferences reference

Note: The table name is app_user (not user) to avoid SQL reserved keyword conflicts.

Security Features

JWT Verification

  • RS256 Signature Verification: Validates token signature using Cognito's public keys
  • Issuer Validation: Ensures token is from your specific Cognito User Pool
  • Token Use Validation: Confirms token is an access or ID token
  • Expiration Check: Automatically rejects expired tokens
  • JWKS Key Caching: Public keys are cached and auto-refreshed

Best Practices

  • Always use HTTPS in production
  • Implement rate limiting
  • Monitor failed verification attempts
  • Keep dependencies updated
  • Use environment-specific configurations

Documentation

Troubleshooting

Token Verification Failed

Issue: Invalid token issuer
Solution: Verify that aws.cognito.userPoolId matches the pool that issued the token. Decode your token at jwt.io and check the iss claim.

Issue: JWT signature verification failed
Solution: Get a fresh token (tokens expire after 1 hour). Ensure your User Pool ID is correct.

Connection Issues

Issue: Failed to fetch JWKS
Solution: Check network connectivity to AWS. Ensure outbound HTTPS traffic is allowed.

Database Issues

Issue: Table "USER" not found
Solution: The service auto-creates tables on startup. Check that spring.jpa.hibernate.ddl-auto=update is set.

CI/CD

This service is designed for containerized deployment:

  • Dockerfile included for building container images
  • Gradle build produces app.jar in bootJar task
  • JaCoCo test coverage reports generated on build
  • Harness CI/CD pipeline configuration in .harness/ directory

Future Enhancements

  • PostgreSQL for production (AWS RDS)
  • User profile update endpoints
  • preferences-service integration