Authentication
All Oho accounts have API access. The API accepts two types of authentication: Basic Authentication and Bearer Token (JWT).
Use Basic Authentication only to retrieve a JWT token, then use the Bearer Token for all subsequent API requests. This provides better security and performance.
Authentication Methods
Method 1: Basic Authentication (Token Retrieval Only)
Basic Authentication uses your username (email) and password encoded in the Authorization header.
⚠️ Security Note: Basic Auth should only be used to retrieve a JWT token, not for regular API requests in production.
How Basic Auth Works
Your credentials are sent in the Authorization header as a base64-encoded string:
Authorization: Basic {base64(username:password)}
Example: Get Your User Details
curl -X GET https://app.weareoho.com/api/users/me \
-H "Authorization: Basic {BASE64_ENCODED_CREDENTIALS}"
Encoding your credentials:
# Python
import base64
credentials = "user@example.com:your_password"
encoded = base64.b64encode(credentials.encode()).decode()
print(f"Authorization: Basic {encoded}")
// JavaScript
const credentials = 'user@example.com:your_password';
const encoded = btoa(credentials);
console.log(`Authorization: Basic ${encoded}`);
Response:
{
"id": 123,
"email": "user@example.com",
"first_name": "John",
"surname": "Doe",
"organization": {
"id": 456,
"name": "Your Organization"
}
}
Method 2: JWT Token Authentication (Recommended)
JWT (JSON Web Token) authentication is the recommended method for all API requests after initial token retrieval.
Step 1: Exchange Credentials for JWT Token
Use your user ID (from the /api/users/me response) to request a JWT token:
curl -X POST https://app.weareoho.com/api/users/{YOUR_USER_ID}/token \
-H "Authorization: Basic {BASE64_ENCODED_CREDENTIALS}"
Example:
curl -X POST https://app.weareoho.com/api/users/123/token \
-H "Authorization: Basic dXNlckBleGFtcGxlLmNvbTpwYXNzd29yZA=="
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTYyNzI1MTI5LCJleHAiOjE1NjUzMTcxMjl9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
"iat": 1562725129,
"exp": 1565317129
}
Token Fields:
token- The JWT to use for authenticationiat- Issued at (Unix timestamp)exp- Expires at (Unix timestamp)
Step 2: Use JWT Token for API Requests
Include the JWT token in the Authorization header using the Bearer scheme:
curl -X GET https://app.weareoho.com/api/organizations/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
That's it! Use this token for all subsequent API requests until it expires.
Complete Authentication Flow
Here's a complete example showing the full authentication process:
Using cURL
# Step 1: Get your user ID
curl -X GET https://app.weareoho.com/api/users/me \
-H "Authorization: Basic $(echo -n 'user@example.com:your_password' | base64)"
# Step 2: Exchange credentials for JWT token (use the ID from step 1)
curl -X POST https://app.weareoho.com/api/users/123/token \
-H "Authorization: Basic $(echo -n 'user@example.com:your_password' | base64)"
# Step 3: Use the JWT token for API requests
curl -X GET https://app.weareoho.com/api/organizations/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Using Python (requests)
import requests
import base64
from datetime import datetime
class OhoClient:
def __init__(self, email, password, base_url="https://app.weareoho.com/api"):
self.base_url = base_url
self.email = email
self.password = password
self.token = None
self.token_expiry = None
def _get_basic_auth_header(self):
"""Generate Basic Auth header"""
credentials = f"{self.email}:{self.password}"
encoded = base64.b64encode(credentials.encode()).decode()
return f"Basic {encoded}"
def get_user_id(self):
"""Get user ID using Basic Auth"""
response = requests.get(
f"{self.base_url}/users/me",
headers={"Authorization": self._get_basic_auth_header()}
)
response.raise_for_status()
return response.json()["id"]
def get_token(self):
"""Exchange credentials for JWT token"""
user_id = self.get_user_id()
response = requests.post(
f"{self.base_url}/users/{user_id}/token",
headers={"Authorization": self._get_basic_auth_header()}
)
response.raise_for_status()
data = response.json()
self.token = data["token"]
self.token_expiry = data["exp"]
return self.token
def is_token_expired(self):
"""Check if token is expired or about to expire"""
if not self.token_expiry:
return True
# Refresh if expiring in next 5 minutes
return datetime.now().timestamp() >= (self.token_expiry - 300)
def ensure_authenticated(self):
"""Ensure we have a valid token"""
if not self.token or self.is_token_expired():
self.get_token()
def request(self, method, endpoint, **kwargs):
"""Make authenticated API request"""
self.ensure_authenticated()
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {self.token}"
response = requests.request(
method,
f"{self.base_url}/{endpoint}",
headers=headers,
**kwargs
)
# Handle token expiry
if response.status_code == 401:
self.get_token()
headers["Authorization"] = f"Bearer {self.token}"
response = requests.request(
method,
f"{self.base_url}/{endpoint}",
headers=headers,
**kwargs
)
response.raise_for_status()
return response.json()
# Usage
client = OhoClient("user@example.com", "your_password")
# Get organizations
orgs = client.request("GET", "organizations/me")
print(orgs)
# Submit a WWC check
check_data = {
"type": "vicwwc",
"identifier": "1076131A",
"first_name": "John",
"surname": "Doe"
}
result = client.request("POST", "scan", json=check_data)
print(result)
Using JavaScript (Node.js)
const axios = require('axios');
class OhoClient {
constructor(email, password, baseURL = 'https://app.weareoho.com/api') {
this.baseURL = baseURL;
this.email = email;
this.password = password;
this.token = null;
this.tokenExpiry = null;
}
getBasicAuthHeader() {
const credentials = `${this.email}:${this.password}`;
const encoded = Buffer.from(credentials).toString('base64');
return `Basic ${encoded}`;
}
async getUserId() {
const response = await axios.get(`${this.baseURL}/users/me`, {
headers: { Authorization: this.getBasicAuthHeader() }
});
return response.data.id;
}
async getToken() {
const userId = await this.getUserId();
const response = await axios.post(
`${this.baseURL}/users/${userId}/token`,
{},
{ headers: { Authorization: this.getBasicAuthHeader() } }
);
this.token = response.data.token;
this.tokenExpiry = response.data.exp;
return this.token;
}
isTokenExpired() {
if (!this.tokenExpiry) return true;
// Refresh if expiring in next 5 minutes
return Date.now() / 1000 >= (this.tokenExpiry - 300);
}
async ensureAuthenticated() {
if (!this.token || this.isTokenExpired()) {
await this.getToken();
}
}
async request(method, endpoint, data = null, config = {}) {
await this.ensureAuthenticated();
try {
const response = await axios({
method,
url: `${this.baseURL}/${endpoint}`,
data,
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
...config.headers
},
...config
});
return response.data;
} catch (error) {
// Handle token expiry
if (error.response?.status === 401) {
await this.getToken();
const response = await axios({
method,
url: `${this.baseURL}/${endpoint}`,
data,
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
...config.headers
},
...config
});
return response.data;
}
throw error;
}
}
}
// Usage
(async () => {
const client = new OhoClient('user@example.com', 'your_password');
// Get organizations
const orgs = await client.request('GET', 'organizations/me');
console.log(orgs);
// Submit a WWC check
const checkData = {
type: 'vicwwc',
identifier: '1076131A',
first_name: 'John',
surname: 'Doe'
};
const result = await client.request('POST', 'scan', checkData);
console.log(result);
})();
Token Management
Token Expiry
JWT tokens issued by the Oho API have an expiration time included in the token payload:
- Token lifetime: Typically 30 days (720 hours)
- Expiry field:
exp(Unix timestamp) - Issued at field:
iat(Unix timestamp)
You can decode the JWT to check the expiry date without validating it. However, tokens may be revoked before expiry, so always handle 401 responses gracefully.
Decoding JWT Tokens
JWT tokens contain three parts separated by dots: header.payload.signature
# Python - decode JWT without verification (to check expiry)
import json
import base64
def decode_jwt_payload(token):
# Split token and get payload (middle part)
payload = token.split('.')[1]
# Add padding if necessary
payload += '=' * (4 - len(payload) % 4)
# Decode base64
decoded = base64.b64decode(payload)
return json.loads(decoded)
token_data = decode_jwt_payload(your_token)
print(f"Expires at: {token_data['exp']}")
print(f"Issued at: {token_data['iat']}")
// JavaScript - decode JWT without verification
function decodeJWT(token) {
const payload = token.split('.')[1];
const decoded = Buffer.from(payload, 'base64').toString();
return JSON.parse(decoded);
}
const tokenData = decodeJWT(yourToken);
console.log(`Expires at: ${tokenData.exp}`);
console.log(`Issued at: ${tokenData.iat}`);
Token Refresh Strategy
Your client code should:
- Store the token and expiry time after retrieval
- Check expiry before each request (with a 5-minute buffer)
- Refresh proactively if token is about to expire
- Handle 401 responses by refreshing the token and retrying
See the complete Python and JavaScript examples above for implementation patterns.
Checking Authentication Status
Verify your authentication by calling the /api/users/me endpoint:
curl -X GET https://app.weareoho.com/api/users/me \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Success Response:
{
"id": 123,
"email": "user@example.com",
"first_name": "John",
"surname": "Doe",
"is_verified": true,
"organization": {
"id": 456,
"name": "Your Organization",
"abn": "12345678901",
"is_active": true
}
}
Error Response (Invalid/Expired Token):
{
"status": 401,
"message": "You are not authorized to view this resource",
"field": "authentication"
}
Security Best Practices
1. Keep Credentials Secure
DO:
- ✅ Store credentials in environment variables
- ✅ Use secrets management systems (AWS Secrets Manager, HashiCorp Vault, etc.)
- ✅ Store JWT tokens securely in memory or encrypted storage
- ✅ Use HTTPS for all API requests
DON'T:
- ❌ Commit credentials or tokens to Git repositories
- ❌ Share passwords or tokens via email or chat
- ❌ Hardcode credentials in client-side JavaScript
- ❌ Log credentials, passwords, or tokens in application logs
- ❌ Use Basic Auth for regular API requests (only for token retrieval)
2. Use Environment Variables
# .env file (never commit this!)
OHO_EMAIL=user@example.com
OHO_PASSWORD=your_password
# Python
import os
email = os.environ.get('OHO_EMAIL')
password = os.environ.get('OHO_PASSWORD')
// JavaScript/Node.js
const email = process.env.OHO_EMAIL;
const password = process.env.OHO_PASSWORD;
3. Rotate Credentials Regularly
- Change passwords every 90 days minimum
- Rotate immediately if compromised
- Use strong, unique passwords for each account
4. Use HTTPS Only
All API requests must use HTTPS. HTTP requests will be rejected.
# ✅ Correct
https://app.weareoho.com/api/constituents
# ❌ Wrong - will fail
http://app.weareoho.com/api/constituents
5. Handle Token Expiry Gracefully
- Implement automatic token refresh logic
- Monitor token expiry timestamps
- Handle 401 Unauthorized responses by refreshing and retrying
- See the complete client examples above for best practices
6. Secure Token Storage
Server-side applications:
- Store tokens in memory or encrypted application cache
- Never write tokens to disk in plain text
- Clear tokens when they expire
Client-side applications:
- Use secure storage mechanisms (e.g., HTTPOnly cookies for web apps)
- Never store tokens in localStorage in browser applications
- Consider using server-side proxy for sensitive API calls
Single Sign-On (SSO) with SAML
Oho supports Single Sign-On (SSO) through the Security Assertion Markup Language (SAML) protocol for enterprise organizations.
What is SAML SSO?
SAML allows your organization to use your existing identity provider (such as Azure AD, Okta, Google Workspace, etc.) to authenticate users accessing Oho. This means:
- Users sign in once with their corporate credentials
- No separate Oho password to remember
- Centralized access management through your identity provider
- Enhanced security through your organization's authentication policies
Setting Up SAML SSO
- Sign in to Oho at app.weareoho.com/login
- Navigate to Settings → SAML page
- Configure your SAML integration using the setup wizard
Need Help with SSO Setup?
SAML configuration varies by identity provider. Contact our support team for assistance:
- Email: support@weareoho.com
- Subject: SAML SSO Setup for [Your Organization]
Include your identity provider name (e.g., "Azure AD", "Okta") in your request, and we'll guide you through the configuration process.
SAML SSO may require a specific subscription plan. Contact support@weareoho.com to confirm your plan includes SSO access.
Multifactor Authentication (MFA/2FA)
Oho supports multifactor authentication (MFA), also known as two-factor authentication (2FA), to provide an additional layer of security for administrator users accessing the Oho platform.
What is Multifactor Authentication?
Multifactor authentication (MFA) requires users to provide two or more verification factors to access their account:
- Something you know - Your password
- Something you have - A time-based one-time password (TOTP) from an authenticator app
This significantly improves account security by requiring both your password and a time-sensitive code that changes every 30 seconds.
MFA Options in Oho
Oho supports multiple approaches to MFA:
1. Okta MFA
- If your organization uses Okta as an identity provider
- MFA managed through your Okta account
- Centralized MFA policy management
2. Your SSO Provider
- If you use SAML SSO with another provider (Azure AD, Google Workspace, etc.)
- MFA enforced through your identity provider's policies
- No additional configuration in Oho required
3. Oho Native MFA
- Built-in MFA capability using TOTP authenticator apps
- Works with any standard authenticator application
- Configured directly in your Oho profile
Enabling MFA in Oho
If your organization doesn't use SSO or if you want to enable native Oho MFA:
Requirements:
- Active Oho user account
- Authenticator application installed on your phone
Supported Authenticator Apps:
- Google Authenticator (iOS/Android)
- Microsoft Authenticator (iOS/Android)
- Authy (iOS/Android/Desktop)
- 1Password (with TOTP support)
- Any TOTP-compatible authenticator app
Step-by-Step: Enable MFA
1. Navigate to Your Profile
- Sign in to Oho at app.weareoho.com
- Click on your profile icon or name
- Select Profile or Account Settings
2. Access MFA Settings
- Look for the MFA or Security section
- Navigate to Profile → MFA
3. Register Your Device
- If MFA is not already enabled, you'll see a "Register device" button
- Click "Register device" to begin setup
4. Follow On-Screen Instructions
- Oho will display a QR code
- Open your authenticator app on your phone
5. Scan the QR Code
- In your authenticator app, select "Add account" or "+"
- Choose "Scan QR code" or similar option
- Point your camera at the QR code displayed in Oho
- Your authenticator app will automatically add Oho
6. Enter Verification Code
- Your authenticator app will display a 6-digit code
- This code changes every 30 seconds
- Enter the current code in Oho to confirm setup
- Click Verify or Confirm
7. Save Backup Codes (if provided)
- Oho may provide backup recovery codes
- Save these codes in a secure location
- Use backup codes if you lose access to your authenticator device
8. MFA Enabled
- MFA is now active on your account
- You'll need your authenticator code at each login
Logging In with MFA
After enabling MFA, your login process changes:
Step 1: Enter Credentials
- Go to app.weareoho.com/login
- Enter your email address
- Enter your password
- Click Sign In
Step 2: Enter MFA Code
- Oho prompts for your MFA code
- Open your authenticator app
- Find the Oho entry
- Enter the current 6-digit code
- Click Verify or Submit
Step 3: Access Granted
- You're now signed in to Oho
- Session remains active until you sign out or it expires
Depending on your browser settings, Oho may remember your device and not require MFA for every login. However, it will always require MFA periodically for security.
Managing Your MFA Device
Viewing MFA Status:
- Navigate to Profile → MFA
- See if MFA is enabled
- View registered devices (if multiple device support available)
Removing a Device:
- Go to Profile → MFA
- Select the device to remove (if applicable)
- Click Remove or Unregister
- Confirm removal
Adding Additional Devices:
- Some implementations allow multiple authenticator devices
- Follow the same "Register device" process
- Useful if you have multiple phones or backup devices
Always maintain backup access to your account:
- Save backup codes provided during setup
- Register multiple devices if supported (work phone + personal phone)
- Ensure your email is accessible for account recovery
- Contact your organization admin if locked out
Troubleshooting MFA
"Invalid code" or "Code doesn't work"
Possible causes:
- Time on phone is out of sync
- Entered code that expired (codes change every 30 seconds)
- Wrong account selected in authenticator app
Solutions:
- Sync your device time:
- Ensure your phone's clock is set to automatic/network time
- Go to phone Settings → Date & Time → Set Automatically
- Wait for new code:
- Authenticator codes expire quickly
- Wait for a fresh code to appear before entering
- Check correct account:
- Verify you're looking at the Oho entry in your authenticator
- Some apps show multiple accounts
"Lost my phone / Lost access to authenticator"
Solutions:
- Use backup codes:
- Enter a backup recovery code instead of authenticator code
- Each backup code typically works once
- Contact your organization administrator:
- Organization admins can reset MFA for your account
- You'll need to re-register your new device
- Contact Oho support:
- Email support@weareoho.com
- Provide account details for identity verification
- Support can assist with account recovery
"Can't find MFA option in profile"
Possible causes:
- Your organization uses SSO with MFA enforced at identity provider level
- MFA not available for your subscription plan
- You're not an administrator user
Solutions:
- Check with your organization administrator about SSO/MFA policies
- Contact support@weareoho.com to confirm MFA availability for your plan
"QR code won't scan"
Solutions:
- Ensure good lighting when scanning
- Try manual entry:
- Most authenticator apps offer "Enter key manually" option
- Oho should display the setup key alongside QR code
- Enter the key in your authenticator app
- Try different authenticator app if one isn't working
MFA Best Practices
1. Use a Reliable Authenticator App
- ✅ Choose a well-maintained app (Google Authenticator, Microsoft Authenticator, Authy)
- ✅ Keep your authenticator app updated
- ❌ Don't use SMS-based 2FA if avoidable (less secure)
2. Back Up Your MFA
- ✅ Save backup codes in secure location (password manager, secure note)
- ✅ Register multiple devices if supported
- ✅ Consider using Authy (cloud backup of tokens) for easier device transitions
- ❌ Don't screenshot QR codes and leave them unprotected
3. Protect Your Devices
- ✅ Use device passcode/biometric lock on phone
- ✅ Enable face ID/fingerprint for authenticator app if available
- ✅ Keep authenticator app protected
- ❌ Don't share your authenticator codes with anyone
4. Plan for Device Loss
- ✅ Save backup codes before traveling
- ✅ Know how to contact your admin for MFA reset
- ✅ Consider registering both work and personal devices
- ✅ Test backup codes to ensure they work
5. Time Synchronization
- ✅ Keep phone time set to automatic
- ✅ Sync time periodically (most authenticator apps have sync function)
- ❌ Don't manually adjust phone time if using MFA
Use a password manager (1Password, Bitwarden, LastPass) to:
- Generate and store strong passwords
- Store backup codes securely
- Some password managers include built-in TOTP support
- Centralize your security credentials
MFA and API Access
Important: Multifactor authentication applies to web login only, not API authentication.
For API Access:
- Use JWT tokens as described in the authentication flow above
- MFA doesn't affect programmatic API access
- Your API credentials (email/password for token exchange) remain the same
- Token-based authentication provides security for API requests
Security Consideration:
- Keep API credentials even more secure when MFA is enabled
- If someone obtains API credentials, they can access API even with MFA on web
- Use environment variables and secrets management for API credentials
- Rotate API access tokens/passwords regularly
User Permissions
User access is controlled at the account level. Each user has access to their organization's data based on their role within that organization. Contact support to manage user roles and permissions.
Troubleshooting
401 Unauthorized
Problem: Your request returns a 401 status code.
Possible Causes:
- JWT token has expired
- JWT token is invalid or malformed
- Basic Auth credentials are incorrect
- Token not included in Authorization header
Solutions:
- Check if token has expired (decode JWT and check
expfield) - Refresh the token using Basic Auth
- Verify credentials are correct (email and password)
- Ensure Authorization header is properly formatted:
- Basic Auth:
Authorization: Basic {base64_encoded} - Bearer Token:
Authorization: Bearer {jwt_token}
- Basic Auth:
- Implement automatic token refresh in your client (see examples above)
403 Forbidden
Problem: Your request returns a 403 status code.
Possible Causes:
- User doesn't have permission for the requested operation
- Trying to access resources from another organization
- Account is not verified
Solutions:
- Verify your email address (check for verification email)
- Ensure your organization is active and in good standing
- Confirm you're accessing resources that belong to your organization
- Contact support if you believe you should have access
404 Not Found - User Token Endpoint
Problem: Getting 404 when calling /api/users/{id}/token
Solutions:
- Verify you're using the correct user ID from
/api/users/me - Ensure the endpoint URL is correct:
POST /api/users/{YOUR_USER_ID}/token - Check you're using POST method, not GET
Invalid Credentials
Problem: Basic Auth returns "invalid credentials" or similar error
Solutions:
- Verify email address is correct (username)
- Check password is correct (no extra spaces or characters)
- Ensure base64 encoding is correct
- Try resetting your password if necessary
Token Decoding Errors
Problem: Can't decode JWT token
Solutions:
- Ensure you're splitting on
.correctly (token has 3 parts) - Add proper base64 padding if necessary
- Use a JWT library for production code (e.g.,
PyJWT,jsonwebtoken) - Verify you copied the entire token without truncation
Next Steps
Now that you're authenticated, explore the Oho API:
- 5-Minute Quickstart - Submit your first check right now
- How Oho Works - Understand Organizations, Constituents, and Accreditations
- Check Types - Learn about all available check types
- API Reference - Explore available API endpoints
- WWC Integration Guide - Step-by-step integration example
Need Help?
- Documentation: Browse our comprehensive API documentation
- Support: Contact support@weareoho.com
- Issues: Report technical issues on GitHub