How It Works

1

User initiates login

User enters username on your login page

2

Your server calls AffirmID

Create an auth request via our API

3

Push sent to user

User receives notification on their registered device

4

User approves/denies

User taps Approve or Deny in the app

5

Result returned

Your server receives the decision via webhook or polling

Key Features

One-Tap Approval

Users approve or deny requests with a single tap on their mobile device.

Rich Context

Show location, device, app name, and custom messages in the push notification.

Configurable Timeout

Set custom expiration times from 30 seconds to 10 minutes.

Biometric Confirmation

Optionally require Face ID or fingerprint before approval.

Implementation

1. Create an Authentication Request

const request = await affirmid.auth.create({
  // Required
  userId: 'user_123',           // Your internal user ID

  // Recommended
  application: 'Acme Dashboard', // Shown in the push notification
  ipAddress: req.ip,            // For location context
  userAgent: req.headers['user-agent'],

  // Optional
  message: 'Login from Chrome on Windows',  // Custom message
  timeout: 120,                 // Seconds until expiration (default: 60)
  requireBiometric: true,       // Require Face ID/fingerprint

  // Custom data (returned in webhooks)
  metadata: {
    sessionId: 'sess_abc123',
    loginAttemptId: 'attempt_xyz'
  }
});

console.log(request);
// {
//   id: 'auth_xxxxxxxxxxxx',
//   status: 'pending',
//   expiresAt: '2024-01-15T12:00:00Z',
//   userId: 'user_123'
// }

2. Wait for the Result

You can either poll for the result or use webhooks:

Option A: Polling

// Simple polling
const result = await affirmid.auth
  .waitForResult(request.id, {
    timeout: 60000,  // 60 seconds
    interval: 1000   // Check every second
  });

if (result.decision === 'approved') {
  // Grant access
}

Option B: Webhooks

// Express webhook handler
app.post('/webhooks/affirmid',
  affirmid.webhooks.verify(),
  (req, res) => {
    const { authRequestId,
            decision } = req.body;
    // Update your session
    res.sendStatus(200);
  }
);

3. Handle the Decision

switch (result.decision) {
  case 'approved':
    // User approved - grant access
    await createSession(result.userId);
    res.redirect('/dashboard');
    break;

  case 'denied':
    // User explicitly denied
    res.status(401).json({ error: 'Authentication denied by user' });
    break;

  case 'expired':
    // Request timed out
    res.status(401).json({ error: 'Authentication request expired' });
    break;
}

// Full result object:
// {
//   id: 'auth_xxxxxxxxxxxx',
//   decision: 'approved',
//   decidedAt: '2024-01-15T12:00:30Z',
//   deviceId: 'device_yyyyyyy',
//   location: { city: 'San Francisco', country: 'US' },
//   biometricUsed: true,
//   metadata: { sessionId: 'sess_abc123' }
// }

Best Practices

Always include context

Provide IP address, user agent, and location so users can make informed decisions about approval.

Set appropriate timeouts

60 seconds works well for most cases. Increase for mobile-first workflows where users might need more time.

Use webhooks in production

Webhooks are more efficient than polling and provide real-time results without keeping connections open.

Provide fallback options

Allow users to authenticate via TOTP if push notifications fail or their device is unavailable.

Error Handling

Error CodeDescriptionResolution
user_not_foundNo user with this ID existsVerify the userId matches your system
no_devicesUser has no registered devicesPrompt user to set up the mobile app
push_failedFailed to deliver push notificationFall back to TOTP authentication
rate_limitedToo many requests for this userWait before retrying

Next Steps

Explore other authentication methods and advanced features.