Enable your app to securely authenticate users and access their DRIP profile data with explicit consent. This OAuth-like flow works for both single-realm and multi-realm applications.

Overview

The DRIP User Authentication system provides a secure, consent-based flow for apps to access user data. Similar to OAuth 2.0, users must explicitly authorize your app before it can access their information.

Quick Flow Summary

  1. Your app redirects user to https://app.drip.re/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI
  2. User reviews and approves your app on the DRIP consent page
  3. User is redirected back to your redirect_uri with an authorization code: YOUR_REDIRECT_URI?code=AUTH_CODE&response_type=code
  4. Your app exchanges the code for the user’s DRIP ID by calling the API
  5. Your app can now access user data using the DRIP ID
Key Benefits:
  • 🔒 Secure user consent flow
  • 🎯 Works for both Realm and Multi-realm apps
  • ⚡ Short-lived authorization codes (5-minute TTL)
  • 🔑 Minimal data exposure (only returns dripId)
  • ✅ Transparent app information for users

Authentication Flow

Implementation Guide

Prerequisites

API Client Setup

Your app must have a valid API client (either Realm or App client) with appropriate scopes

Redirect URI

Configure a redirect URI where users will be sent after authorization

Step 1: Redirect to Authorization Page

Direct users to the DRIP consent page where they can review and approve your app:
function initiateUserAuth(clientId, redirectUri) {
  // Redirect user to DRIP consent page
  const authUrl = new URL('https://app.drip.re/oauth/authorize');
  authUrl.searchParams.set('client_id', clientId);
  authUrl.searchParams.set('redirect_uri', redirectUri);
  
  // Redirect the user to the consent page
  window.location.href = authUrl.toString();
  
  // OR return the URL for the user to visit
  return authUrl.toString();
}

// Example usage:
const authorizationUrl = initiateUserAuth(
  'your-client-id',
  'https://yourapp.com/auth/callback'
);
// Result: https://app.drip.re/oauth/authorize?client_id=your-client-id&redirect_uri=https://yourapp.com/auth/callback
What happens on the consent page:
  1. User must be logged into DRIP
  2. App information is displayed (name, logo, developer, verification status)
  3. User can approve or deny access
  4. If approved, user is redirected to your redirect_uri with an authorization code
  5. If denied, user is redirected with an error parameter
Important: Do not confuse the consent page URL (https://app.drip.re/oauth/authorize) with the API endpoint (https://api.drip.re/api/v1/auth/oauth/authorize). Users visit the consent page in their browser - you should never directly call the authorization API endpoint. The consent page handles calling the API internally when the user approves.

Step 2: Handle the Callback

After user approval, they’ll be redirected to your redirect_uri with the authorization code:
https://yourapp.com/auth/callback?code=AUTHORIZATION_CODE&response_type=code
If the user denies access:
https://yourapp.com/auth/callback?error=access_denied

Step 3: Exchange Authorization Code

Exchange the authorization code for the user’s DRIP ID:
async function exchangeAuthCode(appToken, authCode) {
  const response = await fetch('https://api.drip.re/api/v1/auth/oauth/token', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${appToken}`, // Your app's token
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code: authCode
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Exchange failed: ${error.message}`);
  }

  const data = await response.json();
  return data.dripId; // User's DRIP ID
}

Step 4: Get Public Client Information (Optional)

You can fetch your app’s public information to display on your own consent/login page:
async function getAppInfo(clientId) {
  const response = await fetch(
    `https://api.drip.re/api/v1/auth/oauth/client?client_id=${clientId}`
  );

  if (!response.ok) {
    throw new Error('Failed to fetch app information');
  }

  return await response.json();
  // Returns:
  // {
  //   name: "App Name",
  //   description: "App description",
  //   pfp: "https://...",  // Profile picture URL
  //   verified: true,      // Verification status
  //   developer: "Developer Name"
  // }
}

Complete Implementation Example

Here’s a full implementation of the user authentication flow:
const express = require('express');
const app = express();

// Configuration
const CLIENT_ID = process.env.DRIP_CLIENT_ID;
const APP_TOKEN = process.env.DRIP_APP_TOKEN;
const REDIRECT_URI = 'https://yourapp.com/auth/callback';
const REALM_ID = process.env.DRIP_REALM_ID;

// Step 1: Redirect user to DRIP authorization page
app.get('/auth/login', (req, res) => {
  const authUrl = new URL('https://app.drip.re/oauth/authorize');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  
  // Redirect user to DRIP consent page
  res.redirect(authUrl.toString());
});

// Step 2: Handle callback and exchange code
app.get('/auth/callback', async (req, res) => {
  const { code } = req.query;
  
  if (!code) {
    return res.status(400).json({ error: 'No authorization code provided' });
  }

  try {
    // Exchange code for user's DRIP ID
    const response = await fetch('https://api.drip.re/api/v1/auth/oauth/token', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${APP_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code: code
      })
    });

    const data = await response.json();
    
    // Store user's DRIP ID in your database
    const dripId = data.dripId;
    
    // Create session for user
    req.session.dripId = dripId;
    
    res.json({ 
      success: true, 
      message: 'Authentication successful',
      dripId: dripId 
    });
  } catch (error) {
    res.status(500).json({ error: 'Token exchange failed' });
  }
});

// Step 4: Use the DRIP ID to fetch user data
app.get('/user/profile', async (req, res) => {
  const dripId = req.session.dripId;
  
  if (!dripId) {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  try {
    // Now you can use the dripId to fetch user-specific data
    // from your realm using your app's API token
    const response = await fetch(
      `https://api.drip.re/api/v1/realm/${REALM_ID}/members/search?type=drip-id&values=${dripId}`,
      {
        headers: {
          'Authorization': `Bearer ${APP_TOKEN}`
        }
      }
    );

    const userData = await response.json();
    res.json(userData);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch user data' });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Security Considerations

Authorization Code TTL

  • Codes expire in 5 minutes
  • Single-use only (invalidated after exchange)
  • Cannot be reused once exchanged
  • Implement retry logic for expired codes

Token Security

  • Store tokens securely (never in frontend code)
  • Use HTTPS for all API calls
  • Validate redirect URIs
  • Implement CSRF protection

User Privacy

  • Minimal data exposure (only dripId)
  • Explicit user consent required
  • Users can revoke access anytime
  • Clear privacy policy recommended

App Verification

  • Display app info transparently
  • Show verification status to users
  • Include developer information
  • Provide clear app description

Error Handling

Handle common authentication errors gracefully:

Best Practices

User Experience

1

Clear Consent UI

Build a clear consent screen showing:
  • App name and logo
  • Requested permissions
  • Developer information
  • What data will be accessed
2

Smooth Flow

  • Minimize redirects
  • Show loading states
  • Handle errors gracefully
  • Provide clear success feedback
3

Session Management

  • Store DRIP ID securely
  • Implement session expiration
  • Provide logout functionality
  • Clear sessions on errors

Implementation Tips

class DripAuthManager {
  constructor(clientId, appToken, realmId) {
    this.clientId = clientId;
    this.appToken = appToken;
    this.realmId = realmId;
    this.redirectUri = 'https://yourapp.com/auth/callback';
    this.sessions = new Map();
  }

  // Generate the authorization URL for user to visit
  getAuthorizationUrl() {
    const authUrl = new URL('https://app.drip.re/oauth/authorize');
    authUrl.searchParams.set('client_id', this.clientId);
    authUrl.searchParams.set('redirect_uri', this.redirectUri);
    return authUrl.toString();
  }

  // Handle the callback after user authorization
  async handleCallback(authorizationCode) {
    try {
      // Exchange code for DRIP ID
      const dripId = await this.exchangeCode(authorizationCode);
      
      // Create session
      const sessionId = this.createSession(dripId);
      
      return { success: true, sessionId, dripId };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async exchangeCode(code) {
    const response = await fetch('https://api.drip.re/api/v1/auth/oauth/token', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.appToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code: code
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Exchange failed');
    }

    const data = await response.json();
    return data.dripId;
  }

  createSession(dripId) {
    const sessionId = this.generateSessionId();
    this.sessions.set(sessionId, {
      dripId,
      createdAt: Date.now(),
      lastAccessed: Date.now()
    });
    return sessionId;
  }

  generateSessionId() {
    return Math.random().toString(36).substring(2) + Date.now().toString(36);
  }

  async getUserData(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error('Invalid session');
    }

    // Update last accessed
    session.lastAccessed = Date.now();

    // Fetch user data from DRIP
    const response = await fetch(
      `https://api.drip.re/api/v1/realm/${this.realmId}/members/search?type=drip-id&values=${session.dripId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.appToken}`
        }
      }
    );

    if (!response.ok) {
      throw new Error('Failed to fetch user data');
    }

    return await response.json();
  }

  logout(sessionId) {
    return this.sessions.delete(sessionId);
  }
}

// Usage
const authManager = new DripAuthManager(
  process.env.DRIP_CLIENT_ID,
  process.env.DRIP_APP_TOKEN,
  process.env.DRIP_REALM_ID
);

Testing Your Implementation

Test Authorization Flow

async function testAuthFlow() {
  const clientId = 'your_client_id';
  const appToken = 'your_app_token';
  const redirectUri = 'http://localhost:3000/callback';

  try {
    // Step 1: Generate authorization URL
    console.log('🔐 Authorization URL:');
    const authUrl = new URL('https://app.drip.re/oauth/authorize');
    authUrl.searchParams.set('client_id', clientId);
    authUrl.searchParams.set('redirect_uri', redirectUri);
    console.log(authUrl.toString());
    console.log('👆 User must visit this URL and approve the app');
    
    // Step 2: After user approves, they'll be redirected with a code
    // Simulate receiving the authorization code from the callback
    const authorizationCode = 'AUTHORIZATION_CODE_FROM_CALLBACK';
    console.log('✅ Authorization code received:', authorizationCode);

    // Step 3: Exchange code
    console.log('🔄 Exchanging code for DRIP ID...');
    const tokenResponse = await fetch('https://api.drip.re/api/v1/auth/oauth/token', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${appToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code: authorizationCode
      })
    });

    const { dripId } = await tokenResponse.json();
    console.log('✅ DRIP ID received:', dripId);
    console.log('🎉 Authentication flow complete!');

    return dripId;
  } catch (error) {
    console.error('❌ Test failed:', error.message);
    throw error;
  }
}

Troubleshooting

Common Issues:
  • Ensure your client has the correct scopes for user data access
  • Verify redirect URIs match exactly (including trailing slashes)
  • Check that authorization codes are exchanged within 5 minutes
  • Confirm your app token has necessary permissions

Next Steps