iOS SDK
Verified identity
Use verified identity for identified community actions, account-based chat, and private report replies. Your authenticated backend decides which user to sign; the iOS app receives only a short-lived credential.
Create the signing secret
An administrator creates the project's identity secret in SDK setup. Store that secret and the internal Project ID in your backend environment. The public App ID passed to AppNorthConfiguration is a different value.
Never ship the secret or signing code in your app. A public App ID, an allowed Origin, and the anonymous voter token do not prove who a person is.
Issue a credential
Authenticate the request using your existing backend session. Obtain the email from that session, not a caller-supplied email. Sign the exact UTF-8 payload below with HMAC-SHA256 and return a hexadecimal signature. expiresAt is integer Unix seconds, with a maximum lifetime of 24 hours; this example uses 15 minutes.
import { createHmac } from "node:crypto";
export function appNorthIdentity(authenticatedUser) {
const projectId = process.env.APPNORTH_PROJECT_ID;
const secret = process.env.APPNORTH_IDENTITY_SECRET;
if (!projectId || !secret) throw new Error("Missing AppNorth identity configuration");
const email = authenticatedUser.email.trim().toLowerCase();
const expiresAt = Math.floor(Date.now() / 1000) + 15 * 60;
const payload = `v1\n${projectId}\n${expiresAt}\n${email}`;
const signature = createHmac("sha256", secret).update(payload).digest("hex");
return { email, expiresAt, signature };
}Return the credential over HTTPS from an authenticated endpoint with Cache-Control: no-store. Do not log the credential: it acts as a bearer credential until it expires.
Install it in the client
Decode your backend response and install it after sign-in and whenever your backend renews it:
import AppNorth
struct IdentityResponse: Decodable {
let email: String
let expiresAt: Int64
let signature: String
}
func applyIdentity(_ response: IdentityResponse, to client: AppNorthClient) throws {
let credential = try AppNorthIdentityCredential(
email: response.email,
expiresAt: response.expiresAt,
signature: response.signature
)
client.setIdentityCredential(credential)
}The SDK keeps credentials in memory and sends them in X-AppNorth-Identity-* headers, never in the query or JSON body. hasValidIdentity(at:) and isValid(at:) check local expiry only; they do not verify HMAC signatures. An expired installed credential blocks requests locally with INVALID_IDENTITY. Renew through your backend rather than silently downgrading the action to anonymous.
Add signed properties
For properties, sign v2\n<projectId>\n<expiresAt>\n<normalizedEmail>\n<canonicalProperties>. Canonical properties are a JSON object serialized with keys sorted alphabetically. Return the same values to the app and pass them using AppNorthIdentityProperty.string, .number, or .boolean in the credential initializer.
There may be at most 20 keys. Keys start with a letter and contain only letters, digits, _, ., or -, up to 64 characters; constructor is forbidden. Values are strings of at most 200 characters, finite numbers, or booleans. Objects, arrays, and null are rejected, and the serialized object must fit in 2 KiB. Omitting properties uses v1; an empty properties object still uses v2. Changing properties on the client invalidates the signature.
End or change the session
Before switching accounts, dismiss SDK screens, stop custom polling, stop and discard any RevenueCat helper, and cancel host-owned requests. Then clear the credential:
import AppNorth
func clearAppNorthIdentity(client: AppNorthClient) {
client.clearIdentityCredential()
}clearIdentityCredential() clears only the credential. Your app must discard account-specific view models and clear or separate its own read cache and offline storage. Install the next identity before recreating screens. Do not persist credentials as long-lived sessions.
Secret rotation invalidates existing signatures and hosted-roadmap sessions. There is no per-person revocation list in this contract; stop issuing credentials to that account and use short expiry, or rotate the project secret when immediate project-wide revocation is needed.
Verify
Submit an identified action with a real backend-issued credential.
Change one signed field, then test an expired credential; both must fail.
Sign out and verify that another account cannot see the previous account's private report or chat.
Rotate the secret in a test project and confirm old credentials no longer work.