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:

13f2a9c1d8e7b4f6a0c5d2e9b1a4f7e3c

Node.js implementation

1// server.js

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

1import hmac
2import hashlib

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

1

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

1

$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.

1

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.