Skip to main content

Authentication Guide

TranscriMed uses OAuth2 authorization code flow for secure partner authentication. This guide covers everything you need to implement authentication in your application.

Overview​

The OAuth2 flow consists of four main steps:

  1. Authorization Request - Redirect users to TranscriMed
  2. User Consent - Users grant permissions to your app
  3. Authorization Code - Receive authorization code via callback
  4. Token Exchange - Exchange code for access tokens

Before You Start​

Register Your Application​

  1. Visit TranscriMed Developer Portal
  2. Create a developer account
  3. Register your application with:
    • Application Name: Display name for users
    • Redirect URIs: Where users return after authorization
    • Scopes: Permissions your app needs
    • Application Type: Web, mobile, or server-to-server

Get Your Credentials​

After registration, you'll receive:

  • Client ID: Public identifier for your application
  • Client Secret: Private key (keep secure!)
  • Redirect URIs: Registered callback URLs
Security

Never expose your client secret in client-side code or commit it to version control. Store it securely in environment variables or secure configuration.

Step 1: Authorization Request​

Redirect users to the authorization endpoint to start the OAuth2 flow.

Authorization URL​

GET https://api.transcrimed.com.br/api/oauth/authorize

Required Parameters​

ParameterTypeDescription
response_typestringMust be code
client_idstringYour application's client ID
redirect_uristringMust match registered URI
scopestringSpace-separated list of scopes
statestringRandom value for CSRF protection

Available Scopes​

Only request scopes you need. Available scopes:

ScopeDescription
medical_records:readRead medical records
medical_records:writeCreate and update medical records
medical_records:deleteDelete medical records
jobs:readRead job status and results
jobs:writeCreate, cancel and manage jobs
worklists:writeSend worklist items from RIS/PACS systems
worklists:manageUpdate worklist item status
worklists:readRead worklist items (internal use)

Example Implementation​

JavaScript (Frontend)​

function initiateOAuth2Flow() {
// Generate random state for CSRF protection
const state = generateRandomString(32);

// Store state in session/localStorage for verification
sessionStorage.setItem('oauth_state', state);

// Build authorization URL
const authUrl = new URL('https://api.transcrimed.com.br/api/oauth/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your_client_id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/oauth/callback');
authUrl.searchParams.set('scope', 'medical_records:read medical_records:write jobs:read');
authUrl.searchParams.set('state', state);

// Open in popup for desktop apps, or redirect for web apps
if (isDesktopApplication()) {
openAuthPopup(authUrl.toString());
} else {
window.location.href = authUrl.toString();
}
}

function openAuthPopup(authUrl) {
const popup = window.open(
authUrl,
'oauth_popup',
'width=500,height=600,scrollbars=yes,resizable=yes'
);

// Listen for authorization response
window.addEventListener('message', function(event) {
if (event.data.type === 'oauth2_callback') {
const { code, state } = event.data;
// Handle authorization code
handleAuthorizationCode(code, state);
popup.close();
}
});
}

function generateRandomString(length) {
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let text = '';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

Python (Backend)​

import secrets
import urllib.parse
from flask import Flask, redirect, session

app = Flask(__name__)

@app.route('/auth/login')
def login():
# Generate state for CSRF protection
state = secrets.token_urlsafe(32)
session['oauth_state'] = state

# Build authorization URL
auth_url = 'https://api.transcrimed.com.br/api/oauth/authorize'
params = {
'response_type': 'code',
'client_id': 'your_client_id',
'redirect_uri': 'https://yourapp.com/oauth/callback',
'scope': 'medical_records:read medical_records:write jobs:read',
'state': state
}

full_url = f"{auth_url}?{urllib.parse.urlencode(params)}"
return redirect(full_url)

Node.js (Backend)​

const express = require('express');
const crypto = require('crypto');
const app = express();

app.get('/auth/login', (req, res) => {
// Generate state for CSRF protection
const state = crypto.randomBytes(32).toString('hex');
req.session.oauth_state = state;

// Build authorization URL
const authUrl = new URL('https://api.transcrimed.com.br/api/oauth/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', process.env.CLIENT_ID);
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/oauth/callback');
authUrl.searchParams.set('scope', 'medical_records:read medical_records:write jobs:read');
authUrl.searchParams.set('state', state);

res.redirect(authUrl.toString());
});

Step 2: Handle Callback​

After user consent, TranscriMed redirects to your registered URI with an authorization code.

Success Callback​

https://yourapp.com/oauth/callback?code=AUTH_CODE&state=CSRF_STATE

Error Callback​

https://yourapp.com/oauth/callback?error=access_denied&error_description=User+denied+access&state=CSRF_STATE

Example Callback Handler​

JavaScript​

function handleOAuthCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');

// Check for errors
if (error) {
console.error('OAuth error:', urlParams.get('error_description'));
return;
}

// Verify state to prevent CSRF attacks
const storedState = sessionStorage.getItem('oauth_state');
if (state !== storedState) {
console.error('Invalid state parameter');
return;
}

// Exchange code for tokens
exchangeCodeForTokens(code);
}

Python​

@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
state = request.args.get('state')
error = request.args.get('error')

# Check for errors
if error:
return f"OAuth error: {request.args.get('error_description')}"

# Verify state
if state != session.get('oauth_state'):
return "Invalid state parameter"

# Exchange code for tokens
tokens = exchange_code_for_tokens(code)

# Store tokens securely
session['access_token'] = tokens['access_token']
session['refresh_token'] = tokens['refresh_token']

return redirect('/dashboard')

Step 3: Token Exchange​

Exchange the authorization code for access and refresh tokens.

Token Endpoint​

POST https://api.transcrimed.com.br/api/oauth/token

Request Parameters​

ParameterTypeDescription
grant_typestringMust be authorization_code
codestringAuthorization code from callback
redirect_uristringSame URI used in authorization
client_idstringYour application's client ID
client_secretstringYour application's client secret

Example Implementation​

JavaScript​

async function exchangeCodeForTokens(authCode) {
try {
const response = await fetch('https://api.transcrimed.com.br/api/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authCode,
redirect_uri: 'https://yourapp.com/oauth/callback',
client_id: 'your_client_id',
client_secret: 'your_client_secret'
})
});

if (!response.ok) {
throw new Error(`Token exchange failed: ${response.status}`);
}

const tokens = await response.json();

// Store tokens securely
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('expires_at', Date.now() + (tokens.expires_in * 1000));

return tokens;
} catch (error) {
console.error('Token exchange error:', error);
throw error;
}
}

Python​

import requests

def exchange_code_for_tokens(auth_code):
token_url = 'https://api.transcrimed.com.br/api/oauth/token'

data = {
'grant_type': 'authorization_code',
'code': auth_code,
'redirect_uri': 'https://yourapp.com/oauth/callback',
'client_id': 'your_client_id',
'client_secret': 'your_client_secret'
}

response = requests.post(token_url, data=data)

if response.status_code != 200:
raise Exception(f'Token exchange failed: {response.text}')

return response.json()

Token Response​

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "refresh_token_string_here",
"scope": "medical_records:read medical_records:write jobs:read"
}

Step 4: Using Access Tokens​

Include the access token in the Authorization header for all API requests:

Authorization: Bearer your_access_token_here

Example API Call​

async function listMedicalRecords(accessToken) {
const response = await fetch('https://api.transcrimed.com.br/api/v1/medical-records', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});

if (!response.ok) {
throw new Error(`API call failed: ${response.status}`);
}

return response.json();
}

Token Refresh​

Access tokens expire after 1 hour. Use refresh tokens to obtain new access tokens without requiring user re-authentication.

Refresh Token Request​

POST https://api.transcrimed.com.br/api/oauth/token

Parameters​

ParameterTypeDescription
grant_typestringMust be refresh_token
refresh_tokenstringYour refresh token
client_idstringYour application's client ID
client_secretstringYour application's client secret

Example Implementation​

async function refreshAccessToken(refreshToken) {
try {
const response = await fetch('https://api.transcrimed.com.br/api/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: 'your_client_id',
client_secret: 'your_client_secret'
})
});

if (!response.ok) {
throw new Error('Token refresh failed');
}

const tokens = await response.json();

// Update stored tokens
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('expires_at', Date.now() + (tokens.expires_in * 1000));

// Refresh token may be rotated
if (tokens.refresh_token) {
localStorage.setItem('refresh_token', tokens.refresh_token);
}

return tokens.access_token;
} catch (error) {
console.error('Token refresh error:', error);
// Redirect to login if refresh fails
window.location.href = '/auth/login';
throw error;
}
}

Token Revocation​

Token revocation endpoint will be documented when available. For now, rotate tokens by expiring/refreshing and clearing local credentials on logout.

Security Best Practices​

State Parameter​

Always use the state parameter to prevent CSRF attacks:

// Generate cryptographically secure random state
const state = window.crypto.getRandomValues(new Uint8Array(32))
.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), '');

Client Secret Security​

  • Never expose client secrets in frontend code
  • Store secrets in environment variables
  • Use secure backend proxy for token exchange
  • Rotate secrets regularly

Token Storage​

  • Use secure, httpOnly cookies for web apps
  • Use secure keychain/keystore for mobile apps
  • Implement token encryption for local storage
  • Set appropriate token expiration

PKCE (Proof Key for Code Exchange)​

For enhanced security, especially in mobile/SPA applications, consider implementing PKCE:

// Generate code verifier and challenge
function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return base64UrlEncode(array);
}

function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
return window.crypto.subtle.digest('SHA-256', data)
.then(hash => base64UrlEncode(new Uint8Array(hash)));
}

function base64UrlEncode(array) {
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}

Error Handling​

Handle common OAuth2 errors gracefully:

Authorization Errors​

ErrorDescriptionHandling
access_deniedUser denied accessShow friendly message, allow retry
invalid_clientInvalid client IDCheck configuration
invalid_scopeInvalid scope requestedReview required scopes
server_errorServer errorRetry after delay

Token Errors​

ErrorDescriptionHandling
invalid_grantInvalid/expired codeRestart auth flow
invalid_clientInvalid credentialsCheck client secret
unsupported_grant_typeWrong grant typeCheck request format

Example Error Handler​

function handleOAuthError(error, errorDescription) {
switch (error) {
case 'access_denied':
showMessage('Authorization was denied. Please try again.');
break;
case 'invalid_client':
console.error('Configuration error: Invalid client credentials');
break;
case 'invalid_scope':
console.error('Configuration error: Invalid scope requested');
break;
case 'server_error':
showMessage('Server error. Please try again in a few moments.');
setTimeout(() => window.location.reload(), 5000);
break;
default:
showMessage(`Authentication error: ${errorDescription}`);
}
}

Complete Example​

Here's a complete OAuth2 implementation example:

class TranscriMedAuth {
constructor(clientId, redirectUri) {
this.clientId = clientId;
this.redirectUri = redirectUri;
this.baseUrl = 'https://api.transcrimed.com.br';
}

// Start OAuth2 flow
login(scopes = ['medical_records:read', 'medical_records:write', 'jobs:read']) {
const state = this.generateState();
sessionStorage.setItem('oauth_state', state);

const authUrl = new URL(`${this.baseUrl}/api/oauth/authorize`);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', this.clientId);
authUrl.searchParams.set('redirect_uri', this.redirectUri);
authUrl.searchParams.set('scope', scopes.join(' '));
authUrl.searchParams.set('state', state);

window.location.href = authUrl.toString();
}

// Handle callback
async handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');

if (error) {
throw new Error(`OAuth error: ${urlParams.get('error_description')}`);
}

if (state !== sessionStorage.getItem('oauth_state')) {
throw new Error('Invalid state parameter');
}

return this.exchangeCodeForTokens(code);
}

// Exchange code for tokens
async exchangeCodeForTokens(code) {
const response = await fetch(`${this.baseUrl}/api/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: this.redirectUri,
client_id: this.clientId,
client_secret: this.clientSecret // Only use in backend
})
});

if (!response.ok) {
throw new Error(`Token exchange failed: ${response.status}`);
}

const tokens = await response.json();
this.storeTokens(tokens);
return tokens;
}

// Store tokens securely
storeTokens(tokens) {
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('expires_at', Date.now() + (tokens.expires_in * 1000));
}

// Get current access token
async getAccessToken() {
const token = localStorage.getItem('access_token');
const expiresAt = localStorage.getItem('expires_at');

if (!token) {
throw new Error('No access token available');
}

if (Date.now() >= parseInt(expiresAt)) {
return this.refreshToken();
}

return token;
}

// Refresh access token
async refreshToken() {
const refreshToken = localStorage.getItem('refresh_token');

if (!refreshToken) {
throw new Error('No refresh token available');
}

const response = await fetch(`${this.baseUrl}/api/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: this.clientId,
client_secret: this.clientSecret // Only use in backend
})
});

if (!response.ok) {
// Clear tokens and redirect to login
this.logout();
throw new Error('Token refresh failed');
}

const tokens = await response.json();
this.storeTokens(tokens);
return tokens.access_token;
}

// Logout and revoke tokens
async logout() {
const accessToken = localStorage.getItem('access_token');

if (accessToken) {
try {
await this.revokeToken(accessToken);
} catch (error) {
console.error('Token revocation failed:', error);
}
}

// Clear stored tokens
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('expires_at');
}

// Revoke token
async revokeToken(token) {
await fetch(`${this.baseUrl}/api/oauth/revoke`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token,
token_type_hint: 'access_token',
client_id: this.clientId,
client_secret: this.clientSecret // Only use in backend
})
});
}

// Generate random state
generateState() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
}

// Usage
const auth = new TranscriMedAuth('your_client_id', 'https://yourapp.com/oauth/callback');

// Start login
document.getElementById('login-btn').addEventListener('click', () => {
auth.login(['medical_records:read', 'medical_records:write', 'jobs:read']);
});

// Handle callback (on callback page)
if (window.location.pathname === '/oauth/callback') {
auth.handleCallback()
.then(() => {
window.location.href = '/dashboard';
})
.catch(error => {
console.error('Authentication failed:', error);
alert('Authentication failed. Please try again.');
window.location.href = '/';
});
}

Testing Authentication​

Using Postman​

  1. Import Collection: Use our Postman collection for testing
  2. Set Variables: Configure your client credentials
  3. Test Flow: Run the OAuth2 flow step by step

Using cURL​

# Step 1: Get authorization code (manual browser step)
open "https://api.transcrimed.com.br/api/oauth/authorize?response_type=code&client_id=your_client_id&redirect_uri=https://yourapp.com/callback&scope=medical_records:read&state=random_state"

# Step 2: Exchange code for tokens
curl -X POST https://api.transcrimed.com.br/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=received_code&redirect_uri=https://yourapp.com/callback&client_id=your_client_id&client_secret=your_client_secret"

# Step 3: Use access token
curl -X GET https://api.transcrimed.com.br/api/v1/medical-records \
-H "Authorization: Bearer your_access_token"

Next Steps​

Once authentication is working:

  1. Explore API Reference - Learn about available endpoints
  2. Try Examples - See real-world integration patterns
  3. Use Tools - Simplify integration with our development tools
  4. Contact Support - Get integration help

Need help? Contact our developers for integration assistance.