OSCAR OAuth 1.0a Flow¶
3-legged OAuth token exchange for OSCAR REST API access
Last Updated: 2026-03-09 (v4.3.0)
Overview¶
VitaraVox uses OAuth 1.0a to access OSCAR's REST API (Surface 3: /ws/services/*). This is required for:
- Patient registration (
createPatient→POST /ws/services/demographics) - All REST-mode operations when
preferRest=true(Kai-hosted OSCAR behind Cloudflare WAF) - quickSearch patient lookup (
GET /ws/services/demographics/quickSearch?query=)
The OAuth flow is a standard 3-legged exchange following RFC 5849, with tokens stored encrypted (AES-256-GCM) in the database.
| Property | Value |
|---|---|
| Signature Method | HMAC-SHA1 |
| Token Lifetime | Set to 10 years (TTL=-1 in OSCAR = permanent) |
| Storage | AES-256-GCM encrypted in ClinicConfig |
| Token Monitor | Scheduled job at 6 AM + 6 PM daily |
| Source | routes/oscar-oauth.ts, EmrAdapterFactory.ts:142-186 |
3-Legged OAuth Flow¶
Source: oscar-oauth.ts:45-269
Admin Dashboard VitaraVox Server OSCAR EMR
│ │ │
│ POST /api/oscar-oauth/ │ │
│ initiate {clinicId} │ │
│───────────────────────────►│ │
│ │ │
│ │ Load ClinicConfig │
│ │ Decrypt consumer creds │
│ │ │
│ │ POST /ws/oauth/initiate│
│ │ (OAuth-signed, includes│
│ │ oauth_callback URL) │
│ │───────────────────────►│
│ │ │
│ │ oauth_token + │
│ │ oauth_token_secret │
│ │◄───────────────────────│
│ │ │
│ │ Store in pendingRequests
│ │ (Map, 10min TTL) │
│ │ │
│ { authorizationUrl, state }│ │
│◄───────────────────────────│ │
│ │ │
│ Redirect admin to │ │
│ OSCAR authorization URL │ │
│────────────────────────────────────────────────────►│
│ │ │
│ │ OSCAR shows │
│ │ authorization │
│ │ page to admin │
│ │ │
│ │ Admin clicks │
│ │ "Authorize" │
│ │ │
│ │ GET /api/oscar-oauth/ │
│ │ callback?oauth_token │
│ │ &oauth_verifier │
│ │ &state │
│ │◄───────────────────────│
│ │ │
│ │ Verify CSRF state │
│ │ Match pendingRequest │
│ │ │
│ │ POST /ws/oauth/token │
│ │ (signed with request │
│ │ token + verifier) │
│ │───────────────────────►│
│ │ │
│ │ access_token + │
│ │ access_token_secret │
│ │◄───────────────────────│
│ │ │
│ │ Encrypt + store tokens │
│ │ Set expiry (10 years) │
│ │ Invalidate adapter cache
│ │ Audit log │
│ │ │
│ │ HTML: "OAuth Complete" │
│◄───────────────────────────│ │
API Endpoints¶
POST /api/oscar-oauth/initiate¶
Starts the OAuth flow for a clinic. Source: oscar-oauth.ts:45-135.
| Property | Value |
|---|---|
| Auth | JWT (admin) |
| Body | { clinicId: string } |
| Prerequisites | Clinic must have oscarUrl, oscarConsumerKey, oscarConsumerSecretEnc in DB |
| Response | { authorizationUrl: string, state: string } |
The authorizationUrl points to OSCAR's authorization page: {oscarUrl}/ws/oauth/authorize?oauth_token={token}&state={state}.
GET /api/oscar-oauth/callback¶
Receives the OAuth verifier after admin approves. Source: oscar-oauth.ts:142-269.
| Property | Value |
|---|---|
| Auth | None (OSCAR redirect) |
| Query Params | oauth_token, oauth_verifier, state |
| CSRF Protection | state parameter must match the one from initiate |
| Response | HTML page confirming success |
On success:
1. Exchanges request token + verifier for access token
2. Encrypts access tokens with AES-256-GCM
3. Stores in ClinicConfig.oscarTokenKey + oscarTokenSecretEnc
4. Sets oscarOauthTokenExpiresAt to 10 years from now
5. Invalidates adapter cache for the clinic
6. Writes audit log entry (PHIPA s.10(1) compliance)
Credential Storage¶
All OAuth credentials are stored encrypted in the ClinicConfig table:
| Field | Purpose | Encrypted? |
|---|---|---|
oscarConsumerKey |
OAuth consumer key (from OSCAR admin) | AES-256-GCM |
oscarConsumerSecretEnc |
OAuth consumer secret | AES-256-GCM |
oscarTokenKey |
Access token (from exchange) | AES-256-GCM |
oscarTokenSecretEnc |
Access token secret | AES-256-GCM |
oscarOauthTokenExpiresAt |
Token expiry timestamp | Plaintext (DateTime) |
Encryption uses the ENCRYPTION_KEY env var (64-char hex = 32 bytes). Format: iv:authTag:ciphertext (base64). Source: lib/crypto.ts.
Scope Resolution (CRITICAL)¶
The resolveOAuthCreds() function (EmrAdapterFactory.ts:142-186) resolves credentials with strict scope isolation:
resolveOAuthCreds(clinicConfig, clinicId)
│
▼
┌─────────────────────────────────────┐
│ Clinic has oscarConsumerKey in DB? │
└─────┬──────────────────┬────────────┘
│ YES │ NO
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ CLINIC SCOPE │ │ ENV SCOPE │
│ │ │ │
│ consumerKey │ │ OSCAR_OAUTH_ │
│ from DB │ │ CONSUMER_KEY │
│ consumerSec │ │ OSCAR_OAUTH_ │
│ from DB │ │ CONSUMER_SECRET │
│ tokenKey │ │ OSCAR_OAUTH_ │
│ from DB │ │ TOKEN_KEY │
│ tokenSecret │ │ OSCAR_OAUTH_ │
│ from DB │ │ TOKEN_SECRET │
└──────────────┘ └──────────────────┘
NEVER Mix Scopes
If a clinic has its own oscarConsumerKey, ALL 4 credential fields must come from the clinic DB record. Mixing a clinic consumer key with env token values produces invalid OAuth signatures and routes traffic to the wrong EMR instance. This is enforced in code at EmrAdapterFactory.ts:153-172.
Token Lifetime & Monitoring¶
Token TTL¶
OSCAR tokens generated via the OAuth flow have TTL=-1 (permanent). VitaraVox sets a 10-year expiry as a safety ceiling (oscar-oauth.ts:219).
The oscarOauthTokenExpiresAt field tracks when tokens should be refreshed. This is checked:
- At adapter creation:
EmrAdapterFactory.ts:107passestokenExpiresAtto adapter - Before REST calls: Adapter fails fast if token has expired (
OscarSoapAdapter.ts:527-530) - By scheduled monitor: Runs at 6 AM + 6 PM daily (
scheduler.ts:25-31)
Token Monitor Job¶
The OAuth token monitor runs twice daily and checks all clinics with preferRest=true for tokens approaching expiry. Source: jobs/scheduler.ts:25-31.
TLS Handling¶
For OSCAR instances with self-signed certificates:
ClinicConfig.oscarTlsVerify(boolean, defaulttrue)- When
false: Creates HTTPS agent withrejectUnauthorized: false - Used in both initiate and callback requests
Prerequisites for Clinics¶
Before the OAuth flow can start, a clinic needs:
- OSCAR URL stored in
ClinicConfig.oscarUrl - Consumer credentials from OSCAR admin → stored encrypted in DB
ModuleNames=RESTenabled in OSCAR'soscar.properties(deploys/ws/oauth/*and/ws/services/*)- Tomcat restart after enabling
ModuleNames=REST
Kai-Hosted OSCAR
For Kai-hosted instances, enabling ModuleNames=REST requires a support request to WELL Health / Kai support. This is a one-time configuration change. OSCAR caches the REST module state at Tomcat startup — new OAuth keys also require a Tomcat restart.
Related Documentation¶
- Adapter Implementations — Factory pattern, scope resolution, REST extension
- OSCAR API Protocol Analysis — Three API surfaces, Cloudflare WAF behavior
- Environment Configuration —
OSCAR_OAUTH_*env vars - Database Schema — ClinicConfig OAuth fields