Identity signing
When someone signs in to your app, tell the widget who they are, so their requests follow them from device to device. Your server signs the identity, so nobody can read another person's requests by sending their id.
When it is needed
On Basic, Professional and Enterprise every identity must be signed, and an unsigned one is refused. On Free it is optional, and the project's Configuration page can require it. People who have not signed in can use the widget without an identity at all, if the project allows anonymous visitors.
The verification secret
Create one on the Identity tab of the project's Configuration page. It starts with vs_ and is shown once. Keep it on your server only, for example in OPEM_VERIFICATION_SECRET, and never send it to the browser.
Sign on your server
The signature is HMAC-SHA256 with the secret, as hex, over the person's id, a newline and their email in lower case (nothing after the newline without one). With roles, a newline, the roles sorted and joined with commas, a newline and the time of signing in unix seconds follow. Every sample here gives the same signature for the same person.
import { signIdentity } from "@opem/node";
const signedAt = Math.floor(Date.now() / 1000);
const signature = signIdentity(
{
id: user.id,
email: user.email,
roles: user.roles,
signedAt,
},
process.env.OPEM_VERIFICATION_SECRET, // vs_…
);import hashlib
import hmac
import os
def opem_signature(user_id: str, email: str | None, roles: list[str], signed_at: int) -> str:
"""Sign on your server; signed_at is int(time.time()), passed to Opem.identify() as signedAt."""
message = f"{user_id}\n{(email or '').lower()}"
if roles:
message += "\n" + ",".join(sorted(roles)) + f"\n{signed_at}"
secret = os.environ["OPEM_VERIFICATION_SECRET"].encode()
return hmac.new(secret, message.encode(), hashlib.sha256).hexdigest()require "openssl"
# Sign on your server; signed_at is Time.now.to_i, passed to Opem.identify() as signedAt.
def opem_signature(user_id, email, roles, signed_at)
message = "#{user_id}\n#{email.to_s.downcase}"
roles = Array(roles).sort
message += "\n#{roles.join(",")}\n#{signed_at}" unless roles.empty?
OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("OPEM_VERIFICATION_SECRET"), message)
end<?php
// Sign on your server; $signedAt is time(), passed to Opem.identify() as signedAt.
function opemSignature(string $userId, ?string $email, array $roles, int $signedAt): string
{
$message = $userId . "\n" . strtolower($email ?? '');
sort($roles, SORT_STRING);
if ($roles) {
$message .= "\n" . implode(',', $roles) . "\n" . $signedAt;
}
return hash_hmac('sha256', $message, getenv('OPEM_VERIFICATION_SECRET'));
}package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"os"
"slices"
"strconv"
"strings"
)
// OpemSignature signs on your server; signedAt is time.Now().Unix(), passed to Opem.identify() as signedAt.
func OpemSignature(userID, email string, roles []string, signedAt int64) string {
message := userID + "\n" + strings.ToLower(email)
if len(roles) > 0 {
sorted := slices.Clone(roles)
slices.Sort(sorted)
message += "\n" + strings.Join(sorted, ",") + "\n" + strconv.FormatInt(signedAt, 10)
}
mac := hmac.New(sha256.New, []byte(os.Getenv("OPEM_VERIFICATION_SECRET")))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
}Then identify in the page
Send the signature, and the time of signing when there are roles, to the page with the rest of the person's details, and pass them to Opem.identify().
Opem.identify({
id: user.id,
email: user.email,
name: user.name,
roles: user.roles,
signedAt: user.opemSignedAt,
signature: user.opemSignature, // from your server
});Roles
Roles decide which of your documents the assistant may read for the person: up to 20 names of lower-case letters, digits, _ and -. Opem drops signed roles a day after they were signed, so sign them on each page load. The id and email keep working without roles.