Server Authentication
Secure user identification with HMAC-SHA256 signatures.
Overview
By default, user identification is trust-based — anyone with your project ID could identify as any user. Secure Mode adds server-side verification to prevent identity spoofing.
When enabled, the SDK requires an HMAC-SHA256 signature generated on your server for every identify call. The signature is computed by hashing the user's ID with your workspace's secret key.
Enable Secure Mode from Settings > Login & SSO. Once enabled, identify calls without a valid signature will be rejected.
Get your secret key
Your secret signing key is in the JotReview dashboard under Settings > Login & SSO.
Keep this key private — it should only exist on your server, never in client-side code or bundled into your iOS app. Rotate it immediately if you believe it has been compromised.
The key is a 32-byte hex string that looks like:
Node.js implementation
const JOTREVIEW_SECRET = process.env.JOTREVIEW_SECRET_KEY;
function generateSignature(userId) { return crypto .createHmac('sha256', JOTREVIEW_SECRET) .update(userId) .digest('hex'); }
// Express endpoint
app.get('/api/jotreview-signature', requireAuth, (req, res) => {
const signature = generateSignature(req.user.id);
res.json({ signature });
});
`
Python implementation
JOTREVIEW_SECRET = os.environ['JOTREVIEW_SECRET_KEY']
def generate_signature(user_id: str) -> str: return hmac.new( JOTREVIEW_SECRET.encode('utf-8'), user_id.encode('utf-8'), hashlib.sha256 ).hexdigest()
# Django view from django.http import JsonResponse from django.contrib.auth.decorators import login_required
@login_required
def jotreview_signature(request):
signature = generate_signature(str(request.user.id))
return JsonResponse({'signature': signature})
`
Ruby implementation
JOTREVIEW_SECRET = ENV['JOTREVIEW_SECRET_KEY']
def generate_signature(user_id) OpenSSL::HMAC.hexdigest('SHA256', JOTREVIEW_SECRET, user_id.to_s) end
# Rails controller action class Api::JotReviewController < ApplicationController before_action :authenticate_user!
def signature
render json: { signature: generate_signature(current_user.id) }
end
end
`
PHP implementation
$jotreviewSecret = getenv('JOTREVIEW_SECRET_KEY');
function generateSignature(string $userId): string { global $jotreviewSecret; return hash_hmac('sha256', $userId, $jotreviewSecret); }
// Laravel controller
class JotReviewController extends Controller {
public function signature(Request $request) {
$this->middleware('auth:sanctum');
$signature = generateSignature((string) $request->user()->id);
return response()->json(['signature' => $signature]);
}
}
`
iOS implementation with signature
Fetch the signature from your server and pass it to JotReview.identify. Never compute the signature on the device.
struct AuthManager { func handleSuccessfulLogin(_ user: User) async throws { // Fetch signature from your backend let url = URL(string: "https://api.yourapp.com/jotreview-signature")! var request = URLRequest(url: url) request.setValue("Bearer \(user.accessToken)", forHTTPHeaderField: "Authorization")
// Identify with the server-generated signature JotReview.identify( userId: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, signature: response.signature ) } }
struct SignatureResponse: Decodable {
let signature: String
}
`
If the signature is invalid, JotReview will fall back to anonymous mode and log a warning to the console. Enable Strict Mode in Settings > Login & SSO to reject the session entirely instead of falling back.