Authentication

Macrofy uses a server-side HMAC handshake so your users never interact with Macrofy directly. You keep full ownership of user identity — Macrofy only verifies your signature and issues a scoped session.

How it works

The handshake is a three-party exchange between your backend, the client app, and Macrofy:

  1. Your backend signs a userId with your appSecret (HMAC-SHA256).
  2. The client app passes { appId, userId, signature } to the SDK.
  3. The SDK sends the payload to Macrofy, which verifies the signature and returns a scoped JWT.

Handshake sequence

Your Backend              Client App              Macrofy
     │                        │                      │
     │── signature ──────────▶│                      │
     │   (HMAC-SHA256)        │── connect ──────────▶│
     │                        │                      │── verify signature
     │                        │                      │── check quota
     │                        │◀──── JWT session ────│
     │                        │                      │

Once connected, the SDK persists the session token to the device keychain and restores it automatically on relaunch.


Generate a signature

Your backend computes an HMAC-SHA256 of the userId using your appSecret, then returns the { appId, userId, signature } triple to the client.

Node.js

Since HMAC is a standard cryptographic algorithm, you can generate the signature using the native Node.js crypto module without needing to install any Macrofy-specific packages on your backend.

Node.js — HMAC signature endpoint

import { createHmac } from 'crypto'

export function createMacrofyPayload(userId: string) {
  const appId = process.env.MACROFY_APP_ID!
  const appSecret = process.env.MACROFY_APP_SECRET!
  
  // Generate HMAC-SHA256 signature using native Node.js crypto
  const signature = createHmac('sha256', appSecret)
    .update(userId)
    .digest('hex')

  return { appId, userId, signature }
}

Vercel Serverless Function

app/api/macrofy-signature/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { createHmac } from 'crypto'

export async function POST(req: NextRequest) {
  const { userId } = await req.json()
  if (!userId) {
    return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
  }

  const signature = createHmac('sha256', process.env.MACROFY_APP_SECRET!)
    .update(userId)
    .digest('hex')

  return NextResponse.json({
    appId: process.env.MACROFY_APP_ID,
    userId,
    signature,
  })
}

Supabase Edge Function

supabase/functions/macrofy-signature/index.ts

import { createHmac } from 'node:crypto'

function generateHmac(secret: string, payload: string): string {
  return createHmac('sha256', secret).update(payload).digest('hex')
}

Deno.serve(async (req) => {
  const { userId } = await req.json()
  if (!userId) {
    return new Response(JSON.stringify({ error: 'Missing userId' }), {
      status: 400,
      headers: { 'content-type': 'application/json' },
    })
  }

  const appId = Deno.env.get('MACROFY_APP_ID')!
  const secret = Deno.env.get('MACROFY_APP_SECRET')!
  const signature = generateHmac(secret, userId)

  return new Response(JSON.stringify({ appId, userId, signature }), {
    headers: { 'content-type': 'application/json' },
  })
})

Connect from the SDK

Pass the triple your backend produced to Macrofy.connect(). The SDK validates inputs locally before making a network request.

React Native — connect

import { Macrofy } from '@macrofy/react-native'

const macrofy = new Macrofy()

const session = await macrofy.connect({
  appId: 'your-app-id',
  userId: 'end-user-id',
  signature: 'hmac-signature-from-your-backend',
})

connect() returns an AuthResponse:

AuthResponse

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": "macrofy-scoped-id",
    "appId": "your-app-id",
    "externalId": "end-user-id"
  }
}

The SDK automatically persists token and user via the configured StorageAdapter. The React Native package automatically resolves to use native secure storage via expo-secure-store or similar when available.


Session lifecycle

Restore on relaunch

Call restoreSession() at app startup. If a valid session exists in the keychain, the SDK returns it without a network request.

Restore session

const session = await macrofy.restoreSession()

if (session) {
  // session.token and session.user are available
}

Check connection state

Inspect state

macrofy.isConnected()    // boolean
macrofy.getSessionUser() // SessionUser | null

Disconnect

Clears the JWT and all persisted credentials from the keychain.

Disconnect

await macrofy.disconnect()

Connect endpoint reference

The SDK sends the following payload to the Macrofy gateway:

POST /api/auth/connect

{
  "appId": "aBcDeFgHiJkL",
  "userId": "developer-owned-user-id",
  "signature": "64-char-hex-hmac-sha256"
}
  • Name
    appId
    Type
    string (10-12 char nanoid)
    Description

    The application ID from your Macrofy Dashboard.

  • Name
    userId
    Type
    string
    Description

    Your application's user identifier. Macrofy does not interpret this value — it is echoed back as externalId.

  • Name
    signature
    Type
    string (64-char hex)
    Description

    HMAC-SHA256(appSecret, userId) computed on your server.

Responses

  • Name
    200
    Type
    success
    Description

    Returns { token, user }. The token is a JWT valid for 7 days.

  • Name
    401
    Type
    error
    Description

    Invalid signature — the HMAC does not match.

  • Name
    403
    Type
    error
    Description

    Sandbox quota exceeded — your app has reached its user limit.

Was this page helpful?