Skip to main content

Overview

The authentication system provides secure login, automatic token refresh, and session management for HL7 connectivity operations. All HL7 operations require an active authenticated session.

Authentication Flow

Login Process

The AuthService.login() method establishes an authenticated session:
public void login(
    String email,
    char[] password,
    String apiKey,
    Environment environment
)
email
String
required
User email address for authentication
password
char[]
required
User password (char array for security)
apiKey
String
required
API key provided by the HL7 provider
environment
Environment
required
Target environment (DEV, QA, PRE, or PRD)

Device Request Creation

During login, the client automatically creates a DeviceRequest with unique device identification:
private DeviceRequest createDevice() {
    String deviceId = UUID.randomUUID().toString();
    return new DeviceRequest(deviceId, deviceId, "HL7-Java-Client");
}
DeviceRequest Structure:
messagingid
String
Unique messaging identifier (UUID)
deviceid
String
Unique device identifier (UUID)
devicename
String
Device name (default: “HL7-Java-Client”)
bloqueado
boolean
Device blocked status (always false)
recordar
boolean
Remember device flag (always false)

Login Request Structure

The complete login request sent to the authentication endpoint:
LoginRequest request = new LoginRequest(
    apiKey,
    email,
    new String(password),
    device
);

Login Response

Successful authentication returns a LoginResponse:
LoginResponse loginResponse = JsonUtil.fromJson(response.getBody(), LoginResponse.class);
token
String
JWT access token for authenticated requests
exp
String
Token expiration timestamp (format: yyyyMMddHHmmss)
modelEspecifico
Prestador
Provider-specific data including CUIT and matriculation details

Session Management

SessionContext Initialization

After successful login, the session is initialized with all authentication data:
private void initializeSession(
    LoginResponse response,
    Environment environment,
    DeviceRequest device
) {
    SessionContext.initialize(
        response.getToken(),
        response.getExp(),
        response.getModelEspecifico(),
        environment,
        device
    );
    
    SessionRefreshManager.ensureStarted(this);
}
The SessionContext stores:
  • Authentication token
  • Token expiration timestamp
  • Provider (Prestador) information
  • Environment configuration
  • Device information
SessionContext uses thread-safe atomic operations to manage session state across concurrent operations.

Checking Authentication Status

Verify if a session is active:
if (SessionContext.isAuthenticated()) {
    // Proceed with HL7 operations
}
Implementation (SessionContext.java:25-28):
public static boolean isAuthenticated() {
    SessionState state = STATE.get();
    return state.token() != null && !state.token().isEmpty();
}

Automatic Token Refresh

SessionRefreshManager

The SessionRefreshManager automatically refreshes authentication tokens before expiration: Key Features:
  • Automatic scheduling 2 minutes before token expiration
  • Daemon thread execution (non-blocking)
  • Automatic rescheduling after successful refresh
  • Session cleanup on refresh failure
Refresh Timing Calculation (SessionRefreshManager.java:85-96):
private static long calculateDelaySeconds(String tokenExp) {
    LocalDateTime expTime = LocalDateTime.parse(tokenExp, EXP_FORMAT);
    LocalDateTime refreshTime = expTime.minus(REFRESH_BEFORE);
    
    long delay = Duration
        .between(LocalDateTime.now(), refreshTime)
        .getSeconds();
    
    return Math.max(delay, 5); // Minimum 5 seconds
}
The refresh manager uses a 2-minute buffer (REFRESH_BEFORE = Duration.ofMinutes(2)) to ensure the token is refreshed before expiration.

Refresh Process

The refresh endpoint is called automatically:
private void doRefresh() {
    if (!SessionContext.isAuthenticated()) {
        throw new IllegalStateException("No hay sesión activa para refrescar");
    }
    
    String url = EnvironmentConfig.getAuthRefreshUrl(SessionContext.getEnvironment());
    String body = JsonUtil.toJson(SessionContext.getDevice());
    
    Map<String, String> headers = new HashMap<>();
    headers.put("Authorization", "Bearer " + SessionContext.getToken());
    
    ApiResponse response = apiClient.post(url, body, headers);
    // ... error handling and session update
}
Refresh Success:
SessionContext.updateAuth(
    refreshResponse.getToken(),
    refreshResponse.getExp(),
    refreshResponse.getModelEspecifico()
);

SessionRefreshManager.ensureStarted(this);
Refresh Failure: On HTTP 401/403 or any error, the session is automatically cleared:
logout();  // Clear session
throw new AuthProblemException("Credenciales inválidas en refresh (HTTP " + status + ")");

Logout

Manual Logout

Explicitly terminate the current session:
public void logout() {
    SessionRefreshManager.stop();
    SessionContext.clear();
}
Actions Performed:
  1. Stop the automatic refresh scheduler
  2. Clear all session data from SessionContext
  3. Shutdown the refresh daemon thread
Logout is automatically called on refresh failures to prevent unauthorized operations with expired tokens.

Error Handling

AuthProblemException

Thrown when authentication credentials are invalid or tokens cannot be refreshed:
public class AuthProblemException extends RuntimeException {
    public AuthProblemException(String message) {
        super(message);
    }
}
Scenarios:
  • Invalid credentials during login
  • Token expired and cannot be refreshed (HTTP 401/403)
  • Token revoked by the server
Handling Example:
try {
    authService.login(email, password, apiKey, environment);
} catch (AuthProblemException e) {
    // Credentials are invalid - prompt user to re-enter
    logger.error("Authentication failed: " + e.getMessage());
} catch (RuntimeException e) {
    // Technical error - network, server, etc.
    logger.error("Technical error during login: " + e.getMessage());
}

Login Error Scenarios

HTTP Error Response:
if (response.isHttpError()) {
    throw new RuntimeException(
        "Error técnico durante login (HTTP " + response.getStatusCode() + ")"
    );
}
Empty Response:
if (response.getBody() == null || response.getBody().isEmpty()) {
    throw new RuntimeException(
        "Respuesta vacía del servicio de autenticación"
    );
}
Invalid Response:
if (loginResponse == null || loginResponse.getToken() == null) {
    throw new RuntimeException(
        "Respuesta inválida del servicio de autenticación"
    );
}

Usage Example

import com.hl7client.config.Environment;
import com.hl7client.service.AuthService;
import com.hl7client.client.AuthProblemException;

public class AuthenticationExample {
    
    public static void main(String[] args) {
        AuthService authService = new AuthService();
        
        String email = "[email protected]";
        char[] password = "secure_password".toCharArray();
        String apiKey = "your-api-key";
        Environment environment = Environment.SANDBOX;
        
        try {
            // Perform login
            authService.login(email, password, apiKey, environment);
            System.out.println("Authentication successful!");
            
            // Session is now active and automatically refreshed
            // Perform HL7 operations...
            
            // When done, logout
            authService.logout();
            System.out.println("Logged out successfully");
            
        } catch (AuthProblemException e) {
            System.err.println("Invalid credentials: " + e.getMessage());
        } catch (RuntimeException e) {
            System.err.println("Technical error: " + e.getMessage());
        } finally {
            // Clear password from memory
            java.util.Arrays.fill(password, '\0');
        }
    }
}

Best Practices

Always use char[] for passwords instead of String to allow explicit memory clearing:
char[] password = getPasswordFromUser();
try {
    authService.login(email, password, apiKey, environment);
} finally {
    Arrays.fill(password, '\0'); // Clear from memory
}
The session is automatically managed, but you should explicitly logout when:
  • User logs out manually
  • Application is shutting down
  • Switching between provider accounts
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    authService.logout();
}));
Handle AuthProblemException separately from technical errors:
catch (AuthProblemException e) {
    // Invalid credentials - require user action
    promptForCredentials();
} catch (RuntimeException e) {
    // Technical issue - retry with exponential backoff
    retryWithBackoff();
}
Always use the appropriate environment:
  • Environment.DEV - Development
  • Environment.QA - Quality assurance testing
  • Environment.PRE - Pre-production staging
  • Environment.PRD - Live production operations
Environment env = isProduction() 
    ? Environment.PRD 
    : Environment.DEV;

See Also

Build docs developers (and LLMs) love