Developer GuideAuthentication
Developer Guide

Authentication

JWT-based stateless authentication with dual token system, 2FA, and fine-grained permissions.

Authentication

Neostra uses a stateless JWT-based authentication system with a dual token architecture. The platform supports multi-tenant login flows, two-factor authentication (TOTP), portal access for end users, and method-level authorization with fine-grained permissions.

Token Types

Authentication Flow

The login process is a two-step flow. First, the user authenticates with credentials and receives a temporary token along with a list of tenants they belong to. Then, the user selects a tenant to receive a fully scoped Core JWT.

Sign In

Authenticate with credentials

Send email and password to the sign-in endpoint. On success, you receive a temporary JWT and a list of tenants the user belongs to.

Select a tenant

Use the temporary JWT to call the tenant selection endpoint with the desired tenant ID. This returns a fully scoped Core JWT.

Use the Core JWT

Include the Core JWT in the Authorization header for all subsequent API calls.

# Step 1: Sign in
curl -X POST https://api.neostra.io/v1/auth/signin \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "your-password"
  }'

# Step 2: Select tenant
curl -X POST https://api.neostra.io/v1/auth/select-tenant \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <temporary_jwt>" \
  -d '{
    "tenantId": "tenant-abc-123"
  }'

Sign-In Response

{
  "success": true,
  "message": "Authentication successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tenants": [
      {
        "id": "tenant-abc-123",
        "name": "Acme Corp",
        "logo": "https://storage.neostra.io/logos/acme.png"
      },
      {
        "id": "tenant-xyz-456",
        "name": "Globex Inc",
        "logo": null
      }
    ]
  }
}

User Registration

Registration is invite-based. A user receives an invitation token via email and uses it to complete their registration.

Retrieve invitation details

Call the signup endpoint with the token to get the invitation metadata (email, tenant, role).

Complete registration

Submit user details (name, password) to finalize the account.

# Get invitation info
curl -X GET https://api.neostra.io/v1/auth/signup/INVITE_TOKEN

# Complete registration
curl -X PATCH https://api.neostra.io/v1/auth/signup/INVITE_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jane",
    "lastName": "Doe",
    "password": "SecureP@ssw0rd!"
  }'

Password Reset

Request password reset

Submit the user's email to initiate a reset. A token is sent via email.

Reset the password

Use the token from the email to set a new password.

# Request reset
curl -X POST https://api.neostra.io/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com" }'

# Complete reset
curl -X PATCH https://api.neostra.io/v1/auth/reset-password/RESET_TOKEN \
  -H "Content-Type: application/json" \
  -d '{ "newPassword": "NewSecureP@ss!" }'

Two-Factor Authentication (TOTP)

Neostra supports time-based one-time passwords (TOTP) for an additional layer of security.

Set up 2FA

Call the setup endpoint to receive a QR code and secret key. Scan the QR code with an authenticator app (Google Authenticator, Authy, etc.).

Verify and activate

Submit a TOTP code from the authenticator app to verify the setup. On success, you receive a set of backup codes.

Sign in with 2FA

After enabling 2FA, the sign-in flow will require a TOTP code as an additional parameter.

# Setup 2FA (returns QR code)
curl -X POST https://api.neostra.io/v1/auth/2fa/setup \
  -H "Authorization: Bearer <core_jwt>"

# Verify with TOTP code from authenticator app
curl -X POST https://api.neostra.io/v1/auth/2fa/verify \
  -H "Authorization: Bearer <core_jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "code": "123456" }'

Store backup codes in a secure location. They are only shown once and are required to regain access if you lose your authenticator device.

2FA Setup Response

{
  "success": true,
  "message": "2FA setup initiated",
  "data": {
    "qrCodeUrl": "data:image/png;base64,iVBORw0KGgo...",
    "secret": "JBSWY3DPEHPK3PXP"
  }
}

2FA Verify Response

{
  "success": true,
  "message": "2FA enabled successfully",
  "data": {
    "backupCodes": [
      "A1B2-C3D4",
      "E5F6-G7H8",
      "I9J0-K1L2",
      "M3N4-O5P6",
      "Q7R8-S9T0"
    ]
  }
}

Portal Authentication

The end-user portal uses a separate authentication filter chain with its own token processing.

Portal JWTs are scoped to specific subject request operations. They do not carry the full role and permission set of Core JWTs.

Security Configuration

The platform uses Spring Security with the following configuration:

Authorization and Permissions

Authorization is enforced at the method level using @PreAuthorize annotations with fine-grained permissions.

Permission Format

Permissions follow a resource:action pattern:

PermissionDescription
tenant:viewView tenant details
tenant:editModify tenant settings
users:createInvite new users
users:editUpdate user profiles and roles
assessments:createCreate new assessments
assessments:viewView assessments
subject-requests:createSubmit data subject requests
subject-requests:viewView data subject requests
consent:viewView consent records
consent:manageManage consent configurations

UserDetailsImpl

The authenticated user principal (UserDetailsImpl) carries the following information:

public class UserDetailsImpl {
    String userId;
    String email;
    String tenantId;
    List<String> roles;
    List<String> permissions;
    List<BrandAccess> brandAccess; // brands, processes, sub-processes
}

Example: Checking Permissions

@PreAuthorize("hasAuthority('assessments:create')")
@PostMapping("/api/v1/assessments")
public ResponseEntity<ServiceResponse<Assessment>> createAssessment(
    @RequestBody AssessmentRequest request
) {
    // Only users with assessments:create permission reach this method
}

Token Lifecycle

Tokens are not refreshable. When a Core JWT expires after 180 minutes, the user must re-authenticate through the full sign-in flow.