Authentication - C++ SDK Documentation
Overview
Authentication in BosBase is stateless and token-based. A client is considered authenticated as long as it sends a valid Authorization: YOUR_AUTH_TOKEN header with requests.
Key Points:
- No sessions: BosBase APIs are fully stateless (tokens are not stored in the database)
- No logout endpoint: To “logout”, simply clear the token from your local state (
pb.authStore()->clear()) - Token generation: Auth tokens are generated through auth collection Web APIs or programmatically
- Admin users:
_superuserscollection works like regular auth collections but with full access (API rules are ignored) - OAuth2 limitation: OAuth2 is not supported for
_superuserscollection
Authentication Methods
BosBase supports multiple authentication methods that can be configured individually for each auth collection:
- Password Authentication - Email/username + password
- OTP Authentication - One-time password via email
- OAuth2 Authentication - Google, GitHub, Microsoft, etc.
- Multi-factor Authentication (MFA) - Requires 2 different auth methods
Authentication Store
The SDK maintains an authStore that automatically manages the authentication state:
#include "bosbase/bosbase.h"
using namespace bosbase;
BosBase pb("http://localhost:8090");
// Check authentication status
std::cout << "Is valid: " << pb.authStore()->isValid() << std::endl;
std::cout << "Token: " << pb.authStore()->token() << std::endl;
std::cout << "Record: " << pb.authStore()->record().dump() << std::endl;
// Clear authentication (logout)
pb.authStore()->clear();
Password Authentication
Authenticate using email/username and password. The identity field can be configured in the collection options (default is email).
Backend Endpoint: POST /api/collections/{collection}/auth-with-password
Basic Usage
#include "bosbase/bosbase.h"
using namespace bosbase;
BosBase pb("http://localhost:8090");
// Authenticate with email and password
auto authData = pb.collection("users").authWithPassword(
"test@example.com",
"password123"
);
// Auth data is automatically stored in pb.authStore()
std::cout << "Is valid: " << pb.authStore()->isValid() << std::endl;
std::cout << "Token: " << pb.authStore()->token() << std::endl;
std::cout << "User ID: " << pb.authStore()->record()["id"] << std::endl;
Response Format
// authData contains:
// {
// "token": "eyJhbGciOiJIUzI1NiJ9...",
// "record": {
// "id": "record_id",
// "email": "test@example.com",
// // ... other user fields
// }
// }
Error Handling with MFA
#include "bosbase/error.h"
#include <stdexcept>
try {
pb.collection("users").authWithPassword("test@example.com", "pass123");
} catch (const ClientResponseError& err) {
// Check for MFA requirement
if (err.data().contains("mfaId")) {
auto mfaId = err.data()["mfaId"].get<std::string>();
// Handle MFA flow (see Multi-factor Authentication section)
} else {
std::cerr << "Authentication failed: " << err.what() << std::endl;
}
}
OTP Authentication
One-time password authentication via email.
Backend Endpoints:
POST /api/collections/{collection}/request-otp- Request OTPPOST /api/collections/{collection}/auth-with-otp- Authenticate with OTP
Request OTP
// Send OTP to user's email
auto result = pb.collection("users").requestOTP("test@example.com");
std::cout << "OTP ID: " << result["otpId"] << std::endl; // OTP ID to use in authWithOTP
Authenticate with OTP
// Step 1: Request OTP
auto result = pb.collection("users").requestOTP("test@example.com");
// Step 2: User enters OTP from email
auto authData = pb.collection("users").authWithOTP(
result["otpId"].get<std::string>(),
"123456" // OTP code from email
);
OAuth2 Authentication
Backend Endpoint: POST /api/collections/{collection}/auth-with-oauth2
Manual Code Exchange
// Get auth methods
auto authMethods = pb.collection("users").listAuthMethods();
// Find provider in authMethods["oauth2"]["providers"]
// Exchange code for token (after OAuth2 redirect)
auto authData = pb.collection("users").authWithOAuth2Code(
provider["name"].get<std::string>(),
code,
provider["codeVerifier"].get<std::string>(),
redirectUrl
);
Multi-Factor Authentication (MFA)
Requires 2 different auth methods.
std::string mfaId;
try {
// First auth method (password)
pb.collection("users").authWithPassword("test@example.com", "pass123");
} catch (const ClientResponseError& err) {
if (err.data().contains("mfaId")) {
mfaId = err.data()["mfaId"].get<std::string>();
// Second auth method (OTP)
auto otpResult = pb.collection("users").requestOTP("test@example.com");
pb.collection("users").authWithOTP(
otpResult["otpId"].get<std::string>(),
"123456",
nlohmann::json{{"mfaId", mfaId}} // Pass MFA ID
);
}
}
User Impersonation
Superusers can impersonate other users.
Backend Endpoint: POST /api/collections/{collection}/impersonate/{id}
// Authenticate as superuser
pb.collection("_superusers").authWithPassword("admin@example.com", "adminpass");
// Impersonate a user
auto impersonateClient = pb.collection("users").impersonate(
"USER_RECORD_ID",
3600 // Optional: token duration in seconds
);
// Use impersonate client
auto data = impersonateClient.collection("posts").getFullList();
Auth Token Verification
Verify token by calling authRefresh().
Backend Endpoint: POST /api/collections/{collection}/auth-refresh
try {
auto authData = pb.collection("users").authRefresh();
std::cout << "Token is valid" << std::endl;
} catch (const ClientResponseError& err) {
std::cerr << "Token verification failed: " << err.what() << std::endl;
pb.authStore()->clear();
}
List Available Auth Methods
Backend Endpoint: GET /api/collections/{collection}/auth-methods
auto authMethods = pb.collection("users").listAuthMethods();
std::cout << "Password enabled: " << authMethods["password"]["enabled"] << std::endl;
std::cout << "OAuth2 providers: " << authMethods["oauth2"]["providers"].dump() << std::endl;
std::cout << "MFA enabled: " << authMethods["mfa"]["enabled"] << std::endl;
Complete Examples
Example 1: Complete Authentication Flow with Error Handling
#include "bosbase/bosbase.h"
#include "bosbase/error.h"
#include <iostream>
using namespace bosbase;
nlohmann::json authenticateUser(BosBase& pb, const std::string& email, const std::string& password) {
try {
// Try password authentication
auto authData = pb.collection("users").authWithPassword(email, password);
std::cout << "Successfully authenticated: " << authData["record"]["email"] << std::endl;
return authData;
} catch (const ClientResponseError& err) {
// Check if MFA is required
if (err.status() == 401 && err.data().contains("mfaId")) {
std::cout << "MFA required, proceeding with second factor..." << std::endl;
return handleMFA(pb, email, err.data()["mfaId"].get<std::string>());
}
// Handle other errors
if (err.status() == 400) {
throw std::runtime_error("Invalid credentials");
} else if (err.status() == 403) {
throw std::runtime_error("Password authentication is not enabled for this collection");
} else {
throw;
}
}
}
nlohmann::json handleMFA(BosBase& pb, const std::string& email, const std::string& mfaId) {
// Request OTP for second factor
auto otpResult = pb.collection("users").requestOTP(email);
// In a real app, show a modal/form for the user to enter OTP
// For this example, we'll simulate getting the OTP
std::string userEnteredOTP = getUserOTPInput(); // Your UI function
try {
// Authenticate with OTP and MFA ID
auto authData = pb.collection("users").authWithOTP(
otpResult["otpId"].get<std::string>(),
userEnteredOTP,
nlohmann::json{{"mfaId", mfaId}}
);
std::cout << "MFA authentication successful" << std::endl;
return authData;
} catch (const ClientResponseError& err) {
if (err.status() == 429) {
throw std::runtime_error("Too many OTP attempts, please request a new OTP");
}
throw std::runtime_error("Invalid OTP code");
}
}
// Usage
int main() {
BosBase pb("http://localhost:8090");
try {
authenticateUser(pb, "user@example.com", "password123");
std::cout << "User is authenticated: " << pb.authStore()->record().dump() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Authentication failed: " << e.what() << std::endl;
}
return 0;
}
Example 2: Token Management and Refresh
BosBase note: Calls to
pb.collection("users").authWithPassword()now return static, non-expiring tokens. Environment variables can no longer shorten their lifetime, so the refresh logic below is only required for custom auth collections, impersonation flows, or any token you mint manually.
#include "bosbase/bosbase.h"
#include <iostream>
#include <thread>
#include <chrono>
using namespace bosbase;
bool checkAuth(BosBase& pb) {
if (pb.authStore()->isValid()) {
std::cout << "User is authenticated: " << pb.authStore()->record()["email"] << std::endl;
// Verify token is still valid and refresh if needed
try {
pb.collection("users").authRefresh();
std::cout << "Token refreshed successfully" << std::endl;
return true;
} catch (const ClientResponseError& err) {
std::cout << "Token expired or invalid, clearing auth" << std::endl;
pb.authStore()->clear();
return false;
}
}
return false;
}
// Usage
int main() {
BosBase pb("http://localhost:8090");
if (checkAuth(pb)) {
// User is authenticated
} else {
// Redirect to login
}
return 0;
}
Example 3: Admin Impersonation for Support
#include "bosbase/bosbase.h"
#include <iostream>
using namespace bosbase;
nlohmann::json impersonateUserForSupport(BosBase& pb, const std::string& userId) {
// Authenticate as admin
pb.collection("_superusers").authWithPassword("admin@example.com", "adminpassword");
// Impersonate the user (1 hour token)
auto userClient = pb.collection("users").impersonate(userId, 3600);
std::cout << "Impersonating user: " << userClient.authStore()->record()["email"] << std::endl;
// Use the impersonated client to test user experience
auto userRecords = userClient.collection("posts").getFullList();
std::cout << "User can see " << userRecords.size() << " posts" << std::endl;
// Check what the user sees
auto userView = userClient.collection("posts").getList(1, 10, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"published = true"
);
return nlohmann::json{
{"canAccess", userView["items"].size()},
{"totalPosts", userRecords.size()}
};
}
// Usage in support dashboard
int main() {
BosBase pb("http://localhost:8090");
try {
auto result = impersonateUserForSupport(pb, "user_record_id");
std::cout << "User access check: " << result.dump() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Impersonation failed: " << e.what() << std::endl;
}
return 0;
}
Best Practices
- Secure Token Storage: Never expose tokens in client-side code or logs
- Token Refresh: Implement automatic token refresh before expiration
- Error Handling: Always handle MFA requirements and token expiration
- OAuth2 Security: Always validate the
stateparameter in OAuth2 callbacks - API Keys: Use impersonation tokens for server-to-server communication only
- Superuser Tokens: Never expose superuser impersonation tokens in client code
- OTP Security: Use OTP with MFA for security-critical applications
- Rate Limiting: Be aware of rate limits on authentication endpoints
Troubleshooting
Token Expired
If you get 401 errors, check if the token has expired:
try {
pb.collection("users").authRefresh();
} catch (const ClientResponseError& err) {
// Token expired, require re-authentication
pb.authStore()->clear();
// Redirect to login
}
MFA Required
If authentication returns 401 with mfaId:
if (err.status() == 401 && err.data().contains("mfaId")) {
// Proceed with second authentication factor
}