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
Your app redirects user to https://app.drip.re/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI
User reviews and approves your app on the DRIP consent page
User is redirected back to your redirect_uri with an authorization code: YOUR_REDIRECT_URI?code=AUTH_CODE&response_type=code
Your app exchanges the code for the user’s DRIP ID by calling the API
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:
JavaScript
Python
TypeScript
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:
User must be logged into DRIP
App information is displayed (name, logo, developer, verification status)
User can approve or deny access
If approved, user is redirected to your redirect_uri with an authorization code
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:
JavaScript
Python
TypeScript
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
}
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:
Express.js Server
Flask Server
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:
Error : invalid_clientCauses :
Client ID doesn’t exist
Client has been deactivated
Solution :if ( error . message === 'Invalid clientId' ) {
console . error ( 'Check your CLIENT_ID configuration' );
// Redirect to error page
}
Error : code_already_usedCauses :
Authorization code was already exchanged
Duplicate exchange attempt
Solution :if ( error . message === 'code_already_used' ) {
// Request new authorization code
return initiateNewAuthFlow ();
}
Error : code_expiredCauses :
More than 5 minutes passed since code generation
Network delays
Solution :if ( error . message === 'code_expired' ) {
// Automatically restart auth flow
return restartAuthentication ();
}
Error : invalid_codeCauses :
Malformed authorization code
Code doesn’t exist
Wrong client attempting exchange
Solution :if ( error . message === 'invalid_code' || error . message === 'invalid_code_client' ) {
// Log security event and restart
logSecurityEvent ( 'Invalid auth code attempt' );
return showAuthError ();
}
Best Practices
User Experience
Clear Consent UI
Build a clear consent screen showing:
App name and logo
Requested permissions
Developer information
What data will be accessed
Smooth Flow
Minimize redirects
Show loading states
Handle errors gracefully
Provide clear success feedback
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