Skip to content

OAuth Scopes and Permissions

Scopes define the level of access that your application is requesting from WeTrials users. Users see the requested scopes during the authorization process and can choose to grant or deny access.

Understanding Scopes

What are Scopes?

Scopes are strings that represent specific permissions your application is requesting. They follow a pattern of action:resource to clearly indicate what access is being requested.

Scope Format

<action>:<resource>
  • action: The operation type (read, write, delete)
  • resource: The resource being accessed (profile, studies, participants)

Example Authorization Request

When requesting scopes, include them in the authorization URL:

javascript
const authUrl = new URL('https://auth.wetrials.com/v1/oauth/authorize');
authUrl.searchParams.append('scope', 'read:profile read:studies write:participants');

Users will see a consent screen showing:

  • Your application name
  • Requested permissions in human-readable format
  • Option to approve or deny access

Available Scopes

User Profile Scopes

ScopeDescriptionData Access
read:profileRead user profile informationEmail, name, profile picture, created/updated dates
write:profileUpdate user profileModify name, profile picture, preferences
read:accountRead account settingsSubscription status, billing information, settings
write:accountModify account settingsUpdate preferences, notification settings

Study Management Scopes

ScopeDescriptionData Access
read:studiesView studiesStudy details, protocols, sites, enrollment status
write:studiesCreate and modify studiesCreate new studies, update existing study details
delete:studiesDelete studiesRemove studies and associated data
read:study_participantsView study participantsParticipant lists, enrollment data, demographics
write:study_participantsManage study participantsEnroll/unenroll participants, update status

Participant Scopes

ScopeDescriptionData Access
read:participantsView participant dataBasic participant information, consent status
write:participantsManage participantsCreate participants, update information
read:participant_dataAccess participant research dataSurvey responses, clinical data, outcomes
write:participant_dataSubmit participant dataRecord visits, submit forms, upload documents

Administrative Scopes

ScopeDescriptionData Access
read:organizationsView organization detailsOrganization info, members, settings
write:organizationsManage organizationsUpdate settings, manage members
read:audit_logsView audit trailsSystem logs, user activities, compliance records
read:reportsAccess reportsAnalytics, metrics, compliance reports
write:reportsGenerate reportsCreate custom reports, export data

Communication Scopes

ScopeDescriptionData Access
read:messagesRead messagesInternal messages, notifications
write:messagesSend messagesSend messages to participants, team members
read:notificationsView notificationsSystem notifications, alerts
write:notificationsManage notificationsMark as read, configure preferences

In addition to scopes, WeTrials supports optional consent tags for granular data access control. These are presented separately during authorization.

Available Optional Tags

javascript
// Request optional consents during authorization
const optionalConsents = [
  'analytics', // Share usage analytics
  'marketing', // Receive marketing communications
  'research', // Participate in platform research
  'data_sharing', // Allow data sharing with partners
];

Requesting Optional Consents

Optional consents are handled separately from scopes:

javascript
// In the consent endpoint
const consentData = {
  client_id: 'your_client_id',
  // ... other parameters
  optional_consents: 'analytics,research', // Comma-separated
};

Accessing Consented Tags

Consented optional tags are included in the access token and user info response:

json
{
  "sub": "user_id",
  "email": "user@example.com",
  "consented_optional_tags": ["analytics", "research"]
}

Scope Combinations

Common Scope Patterns

Read-Only Integration

For applications that only need to view data:

read:profile read:studies read:participants

Study Management

For study coordinators and managers:

read:profile read:studies write:studies read:study_participants write:study_participants

Full Administrative Access

For administrative tools:

read:profile write:profile read:studies write:studies delete:studies read:organizations write:organizations read:audit_logs

Participant Portal

For participant-facing applications:

read:profile write:profile read:participant_data write:participant_data read:messages

Incremental Authorization

WeTrials supports incremental authorization, allowing you to request additional scopes as needed.

Initial Authorization

Start with minimal scopes:

javascript
// Initial login - basic profile access
oauth.authorize(['read:profile']);

Requesting Additional Scopes

Later, request additional permissions:

javascript
// User wants to access studies
function requestStudyAccess() {
  // Request additional scopes
  oauth.authorize([
    'read:profile', // Include existing scopes
    'read:studies', // Add new scopes
    'write:studies',
  ]);
}

Checking Current Scopes

Decode the access token to check granted scopes:

javascript
function getGrantedScopes(accessToken) {
  try {
    const payload = JSON.parse(atob(accessToken.split('.')[1]));
    return payload.scopes || [];
  } catch (error) {
    console.error('Failed to decode token:', error);
    return [];
  }
}

// Check if specific scope is granted
function hasScope(accessToken, scope) {
  const scopes = getGrantedScopes(accessToken);
  return scopes.includes(scope);
}

// Usage
if (hasScope(accessToken, 'write:studies')) {
  // Show create study button
  showCreateStudyUI();
}

Scope Validation

Server-Side Validation

Always validate scopes server-side before performing operations:

javascript
// Node.js example
function requireScope(scope) {
  return (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];

    if (!token) {
      return res.status(401).json({ error: 'No token provided' });
    }

    try {
      const decoded = jwt.verify(token, publicKey);

      if (!decoded.scopes.includes(scope)) {
        return res.status(403).json({
          error: 'Insufficient permissions',
          required_scope: scope,
        });
      }

      req.user = decoded;
      next();
    } catch (error) {
      return res.status(401).json({ error: 'Invalid token' });
    }
  };
}

// Protect endpoints with scope requirements
app.get('/api/studies', requireScope('read:studies'), (req, res) => {
  // Handler code
});

app.post('/api/studies', requireScope('write:studies'), (req, res) => {
  // Handler code
});

Client-Side UI Control

Use scopes to control UI elements:

jsx
// React component example
function StudyManager({ accessToken }) {
  const scopes = getGrantedScopes(accessToken);
  const canRead = scopes.includes('read:studies');
  const canWrite = scopes.includes('write:studies');
  const canDelete = scopes.includes('delete:studies');

  return (
    <div>
      {canRead && <StudyList />}
      {canWrite && <CreateStudyButton />}
      {canDelete && <DeleteStudyButton />}
      {!canRead && <NoPermissionMessage />}
    </div>
  );
}

Best Practices

1. Request Minimal Scopes

Only request the scopes you actually need:

javascript
// ✅ GOOD - Minimal scopes
oauth.authorize(['read:profile', 'read:studies']);

// ❌ BAD - Requesting everything
oauth.authorize([
  'read:profile',
  'write:profile',
  'read:studies',
  'write:studies',
  'delete:studies',
  'read:participants',
  'write:participants',
  // ... all scopes
]);

2. Explain Scope Usage

Provide clear explanations before requesting scopes:

jsx
function ScopeExplanation({ requestedScopes }) {
  return (
    <div className="scope-explanation">
      <h3>This application needs permission to:</h3>
      <ul>
        {requestedScopes.includes('read:profile') && <li>View your profile information</li>}
        {requestedScopes.includes('write:studies') && <li>Create and modify studies on your behalf</li>}
        {requestedScopes.includes('read:participants') && <li>Access participant information for your studies</li>}
      </ul>
      <p>You can revoke these permissions at any time in your account settings.</p>
    </div>
  );
}

3. Handle Scope Denial

Gracefully handle when users deny certain scopes:

javascript
// After authorization callback
const grantedScopes = tokens.scope.split(' ');
const requiredScopes = ['read:profile', 'read:studies'];

const missingScopes = requiredScopes.filter((scope) => !grantedScopes.includes(scope));

if (missingScopes.length > 0) {
  // Inform user about limited functionality
  showWarning(`Limited functionality: Missing permissions for ${missingScopes.join(', ')}`);

  // Disable features that require missing scopes
  disableFeatures(missingScopes);
}

4. Progressive Enhancement

Start with basic functionality and enhance with additional scopes:

javascript
class ProgressiveApp {
  constructor() {
    this.baseScopes = ['read:profile'];
    this.enhancedScopes = [];
  }

  async initializeApp() {
    // Start with basic profile access
    await this.authorize(this.baseScopes);
    this.showBasicUI();
  }

  async enableStudyFeatures() {
    // Request additional scopes when needed
    this.enhancedScopes = ['read:studies', 'write:studies'];
    await this.authorize([...this.baseScopes, ...this.enhancedScopes]);
    this.showStudyUI();
  }

  async enableAdminFeatures() {
    // Request admin scopes only for admin users
    this.enhancedScopes.push('read:organizations', 'write:organizations');
    await this.authorize([...this.baseScopes, ...this.enhancedScopes]);
    this.showAdminUI();
  }
}

Scope Changes and Versioning

Handling Scope Changes

WeTrials may occasionally update available scopes. Your application should handle:

  1. New Scopes: Additional permissions become available
  2. Deprecated Scopes: Old scopes are phased out
  3. Scope Splits: One scope becomes multiple specific scopes

Version Management

javascript
// Check API version and available scopes
async function getAvailableScopes() {
  const response = await fetch('https://api.wetrials.com/v1/oauth/scopes');
  const data = await response.json();

  return {
    version: data.version,
    scopes: data.scopes,
    deprecated: data.deprecated_scopes,
  };
}

// Handle deprecated scopes
function mapDeprecatedScopes(requestedScopes) {
  const scopeMapping = {
    full_access: ['read:profile', 'read:studies', 'write:studies'],
    participant_access: ['read:participants', 'write:participants'],
  };

  return requestedScopes.flatMap((scope) => scopeMapping[scope] || [scope]);
}

Testing Scopes

Test Different Scope Combinations

bash
# Test with minimal scopes
curl -G "https://auth.wetrials.com/v1/oauth/authorize" \
  --data-urlencode "scope=read:profile"

# Test with multiple scopes
curl -G "https://auth.wetrials.com/v1/oauth/authorize" \
  --data-urlencode "scope=read:profile read:studies write:studies"

# Test with invalid scope
curl -G "https://auth.wetrials.com/v1/oauth/authorize" \
  --data-urlencode "scope=invalid:scope"

Verify Token Scopes

bash
# Decode JWT to check scopes
echo $ACCESS_TOKEN | cut -d. -f2 | base64 -d | jq '.scopes'

Next Steps