@hmrc-sync/oauth
v1.1.0
Published
OAuth 2.0 token management for HMRC APIs
Readme
@hmrc-sync/oauth
OAuth 2.0 token management for HMRC APIs.
Overview
This package manages HMRC OAuth 2.0 token lifecycle, addressing HMRC-specific quirks including:
- Single-use refresh tokens with race condition prevention
- 18-month session expiry tracking
- Agent multi-account token management
- Crash-safe token rotation
- PKCE support for installed applications
Key Features
- Race Condition Prevention: Mutex-based token refresh with entry clearing on both success and failure to prevent rejected promise propagation
- 18-Month Expiry Tracking: Proactive monitoring using
authorizedAttimestamp (notexpiresAt) to track session health separately from access token health - Single-Use Refresh Tokens: Handles HMRC's constraint that refresh tokens can only be used once with crash-safe atomic storage
- Agent Multi-Account Support: Composition-based design mapping multiple access tokens to client MTD identifiers (NINO/UTR)
- PKCE Support: Optional but recommended for installed applications with temporary storage for callback validation
- Human-Readable Errors: Translates OAuth error codes including HMRC-specific cases (SERVER_ERROR, INVALID_REQUEST sub-codes, HTTP 200 with error bodies)
- Flexible Token Storage: Interface-based design with in-memory (dev) and Redis (production with MULTI/EXEC transactions) implementations
- Observability: Structured logging for all token refresh operations (clientId, timestamp, success/failure, mutex-coalesced status)
Installation
npm install @hmrc-sync/oauthFor production use with Redis store:
npm install ioredis@^5.0.0Basic Usage
import { HmrcOAuthClient, InMemoryTokenStore } from '@hmrc-sync/oauth';
const store = new InMemoryTokenStore();
const config = {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'http://localhost:3000/callback',
scopes: ['read:employment'],
authEndpoint: 'https://test-www.tax.service.gov.uk/oauth',
tokenEndpoint: 'https://test-api.service.hmrc.gov.uk/oauth/token'
};
const client = new HmrcOAuthClient(store, config);
// Build authorization URL
const { url, codeVerifier } = await client.buildAuthorizationUrl();
// Exchange authorization code for tokens
const tokens = await client.exchangeCodeForToken(code, codeVerifier);
// Get valid access token (auto-refreshes if needed)
const accessToken = await client.getValidAccessToken();
// Check token health
const health = await client.checkTokenHealth();
console.log(`Session expires in ${health.daysUntilReauth} days`);Token Store Options
InMemoryTokenStore (Development)
Simple in-memory storage for development and testing. Tokens are lost on restart.
import { InMemoryTokenStore } from '@hmrc-sync/oauth';
const store = new InMemoryTokenStore();RedisTokenStore (Production)
Production-ready Redis storage with atomic transactions for crash-safe token rotation.
import { createRedisStore } from '@hmrc-sync/oauth';
const store = await createRedisStore('redis://localhost:6379');NOTE: Switching token stores requires re-authorisation for all clients (existing tokens don't migrate).
Composition with @hmrc-sync/engine
To combine OAuth tokens with fraud prevention headers for HMRC API calls:
import { HmrcOAuthClient, InMemoryTokenStore } from '@hmrc-sync/oauth';
import { generateHeaders } from '@hmrc-sync/engine';
const oauthClient = new HmrcOAuthClient(store, config);
const accessToken = await oauthClient.getValidAccessToken();
const headers = generateHeaders({
// your vendor config
});
const response = await fetch('https://test-api.service.hmrc.gov.uk/endpoint', {
headers: {
...headers,
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.hmrc.1.0+json'
}
});Agent Multi-Account Usage
For agents managing multiple clients:
import { AgentTokenManager, HmrcOAuthClient, InMemoryTokenStore } from '@hmrc-sync/oauth';
const store = new InMemoryTokenStore();
const config = { /* your config */ };
const oauthClient = new HmrcOAuthClient(store, config);
const agentManager = new AgentTokenManager(oauthClient, store);
// Add client-specific token (clientIdentifier is client's NINO or UTR)
await agentManager.addClientToken('agent-id', 'client-nino', clientTokens);
// Get token for specific client
const tokens = await agentManager.getClientToken('agent-id', 'client-nino');
// List all client identifiers for an agent
const clientIds = agentManager.getClientIdentifiers('agent-id');Error Handling
The package provides human-readable error messages for HMRC-specific error cases:
import { translateOAuthError } from '@hmrc-sync/oauth';
try {
await client.exchangeCodeForToken(code);
} catch (error) {
const userMessage = translateOAuthError(error);
console.log(userMessage.message);
console.log(userMessage.explanation);
console.log(userMessage.action);
}Observability
All token refresh operations are logged with structured information:
- clientId
- timestamp
- success/failure status
- mutex-coalesced status (whether multiple requests were coalesced)
API Reference
Classes
HmrcOAuthClient- Main OAuth client for token operationsAgentTokenManager- Multi-account token management for agentsInMemoryTokenStore- In-memory token storageRedisTokenStore- Redis-based token storage
Utilities
generateCodeVerifier()- Generate PKCE code verifiergenerateCodeChallenge(verifier)- Generate PKCE code challengegeneratePKCE()- Generate both verifier and challengegenerateState()- Generate OAuth state parametervalidateState()- Validate OAuth state parametertranslateOAuthError()- Translate OAuth errors to user-friendly messageshandleCallback()- Handle OAuth callback parameters
Types
HmrcTokens- Token interface with authorizedAt timestampTokenHealth- Token health status (access token vs session health)AuthConfig- OAuth configurationOAuthError- OAuth error responseUserFacingMessage- User-facing error messageTokenStore- Token storage interfacePKCEPair- PKCE code pairCallbackParams- OAuth callback parameters
