Skip to contents

Overview

This vignette covers advanced authentication topics for meetupr users who need:

  • Higher API rate limits
  • Production applications with their own OAuth credentials
  • Fine-grained control over authentication flow
  • Credential management across teams or environments

For basic usage, see the Getting Started vignette. Most users don’t need custom OAuth credentials and can use the package’s built-in authentication.

Why Use Custom OAuth Credentials

The meetupr package includes built-in OAuth credentials that work for most users. However, you should consider registering your own OAuth application if:

  • Rate limits: You’re hitting the default rate limit (500 requests per 60 seconds) and need higher throughput
  • Production apps: You’re building a Shiny app, API, or other production service that uses meetupr
  • Team usage: Multiple people share API access and you want centralized credential management
  • Compliance: Your organization requires using OAuth apps registered under your account
  • Analytics: You want detailed usage analytics from the Meetup developer dashboard

Custom credentials don’t provide access to different data, only better control over how authentication is managed.

Registering a Meetup OAuth Application

Step 1: Create the OAuth App

Navigate to the Meetup OAuth app registration page:

https://www.meetup.com/api/oauth/list/

Click Create New OAuth Consumer and fill in the required fields:

  • Consumer Name: A descriptive name (e.g., “My R Analysis App”)
  • Application Website: Your organization’s website or GitHub repository
  • Redirect URI: http://localhost:1410/ (required for OAuth flow in R)
  • Consumer Description: Brief description of what your app does

Step 2: Note Your Credentials

After creating the app, you’ll receive:

  • Key (Client ID): A unique identifier for your application
  • Secret (Client Secret): A confidential string used to authenticate your app

Important: Keep the client secret confidential. Never commit it to version control or share it publicly. Anyone with your secret can make API requests as your application.

Step 3: Understand OAuth Scopes

Meetup’s API uses scopes to control what data your application can access. The meetupr package requests these scopes by default:

  • basic: Read basic profile information
  • event_management: Read event data and RSVPs
  • group_join: Read group membership information

You can customize scopes when creating your OAuth app, but the defaults work for most meetupr use cases.

Storing Custom Credentials

meetupr uses the keyring package to securely store credentials in your system’s native credential manager.

Using the Keyring

Store your custom credentials:

# Store client ID and secret
meetup_key_set("client_id", "your_client_id_here")
meetup_key_set("client_secret", "your_client_secret_here")

If you don’t provide values, you’ll be prompted interactively:

# Prompts for input
meetup_key_set("client_id")

The credentials are stored securely in:

  • macOS: Keychain
  • Windows: Credential Manager
  • Linux: Secret Service API (via libsecret)

Verifying Stored Credentials

Check what credentials are available:

# Check if credentials exist
key_available("client_id")
key_available("client_secret")

# Retrieve stored values (for debugging only - don't print secrets!)
client_id <- meetup_key_get("client_id")
# Note: Never print client_secret in logs or console

Retrieving Credentials

meetupr automatically retrieves credentials from keyring when needed. The lookup order is:

  1. System keyring (via meetup_key_get())
  2. Environment variables (MEETUP_CLIENT_ID, MEETUP_CLIENT_SECRET)
  3. Built-in package credentials (fallback)

This allows you to override credentials at different levels (system-wide, project-specific, or per-session).

Authenticating with Custom Credentials

Once credentials are stored, authentication works the same as with built-in credentials:

# Authenticate (will use stored credentials automatically)
meetup_auth()

# Verify authentication
meetup_auth_status()

The OAuth flow opens a browser where you:

  1. Log in with your Meetup account
  2. Grant permission to your registered OAuth app
  3. Get redirected back to R with an access token

The token is cached locally and reused until it expires (typically 90 days).

Using Environment Variables

For project-specific configurations, you can use environment variables instead of keyring. Create a .Renviron file in your project:

MEETUP_CLIENT_ID=your_client_id
MEETUP_CLIENT_SECRET=your_client_secret

Important: Add .Renviron to .gitignore to avoid committing secrets.

Environment variables take precedence over keyring, allowing per-project overrides.

Using a Custom Client Name

By default, tokens are cached under the service name "meetupr". If you’re using multiple OAuth apps, you can specify a custom client name:

# Store credentials for a specific app
meetup_key_set("client_id", "app1_client_id")
meetup_key_set("client_secret", "app1_secret")

# Authenticate with custom client name
meetup_auth(client_name = "my_custom_app")

# Use the custom client for API calls
get_group("rladies-lagos", client_name = "my_custom_app")

This allows you to maintain separate authentication for different projects or OAuth applications.

Advanced CI/CD Authentication

For production deployments, automated workflows, or team environments, you need non-interactive authentication.

Understanding CI Authentication Flow

The CI authentication pattern:

  1. Locally (one-time setup): Authenticate interactively and encode the token
  2. CI Environment: Store the encoded token as a secret
  3. CI Runtime: Decode and use the token without browser interaction

This lets automated systems use your OAuth credentials without exposing the client secret.

Setting Up CI Authentication

Step 1: Authenticate Locally

# First, ensure custom credentials are stored
meetup_key_set("client_id", "your_client_id")
meetup_key_set("client_secret", "your_client_secret")

# Authenticate interactively
meetup_auth()

# Generate CI credentials
meetup_ci_setup()

The meetup_ci_setup() function will:

  1. Read your cached OAuth token
  2. Encode it as base64
  3. Store it in keyring under "token" and "token_file" keys
  4. Display the values for setting CI secrets
  5. Provide platform-specific instructions

Output example:

Setting up CI credentials...

Add these secrets to your CI environment:

  meetupr:token:
    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

  meetupr:token_file:
    ae743e0fbd718c21f2cca632e77bd180-token.rds.enc

GitHub Actions:
  Settings > Secrets and variables > Actions > New repository secret
  Name: MEETUPR_TOKEN (value: meetupr:token above)
  Name: MEETUPR_TOKEN_FILE (value: meetupr:token_file above)

Step 2: Configure CI Secrets

GitHub Actions:

Go to repository Settings > Secrets and variables > Actions, then add:

  • MEETUPR_TOKEN: Base64-encoded token (the long string)
  • MEETUPR_TOKEN_FILE: Token filename (e.g., ae743e0-token.rds.enc)

GitLab CI:

Go to Settings > CI/CD > Variables, then add:

  • MEETUPR_TOKEN: Base64-encoded token (mask variable)
  • MEETUPR_TOKEN_FILE: Token filename

Other CI platforms:

Most platforms have a secrets management feature. Store both values as environment variables that will be available at runtime.

Step 3: Load Token in CI Workflow

Add this to the beginning of your CI script:

# At the start of your CI script
meetup_ci_load()

# Now you can use the API
groups <- get_pro_groups("rladies")

For GitHub Actions, your workflow YAML should include:

name: Meetup Data Analysis

on:
  schedule:
    - cron: '0 0 * * 0'  # Weekly on Sunday
  workflow_dispatch:

jobs:
  analyze:
    runs-on: ubuntu-latest
    
    env:
      "meetupr:token": ${{ secrets.MEETUPR_TOKEN }}
      "meetupr:token_file": ${{ secrets.MEETUPR_TOKEN_FILE }}
    
    steps:
      - uses: actions/checkout@v3
      
      - uses: r-lib/actions/setup-r@v2
      
      - name: Install dependencies
        run: |
          install.packages(c("remotes", "meetupr"))
        shell: Rscript {0}
      
      - name: Run analysis
        run: |
          library(meetupr)
          meetup_ci_load()
          
          # Your analysis code here
          groups <- get_pro_groups("rladies")
          saveRDS(groups, "groups.rds")
        shell: Rscript {0}
      
      - name: Upload results
        uses: actions/upload-artifact@v3
        with:
          name: analysis-results
          path: groups.rds

Important: Quote the environment variable names in YAML because they contain colons.

Token Expiration and Refresh

OAuth tokens typically expire after 90 days. When your CI workflow fails with authentication errors:

  1. Re-run meetup_auth() locally to get a fresh token
  2. Run meetup_ci_setup() to regenerate CI credentials
  3. Update the secrets in your CI platform with the new values

Consider setting up calendar reminders to refresh tokens before they expire.

Team Credential Management

For teams using shared CI credentials:

Option 1: Shared Keyring Credentials

Designate one person to:

  1. Register the OAuth app
  2. Store credentials in keyring
  3. Run CI setup and distribute secrets
  4. Refresh tokens when they expire

Option 2: Service Account

Create a dedicated Meetup account for API access:

  1. Register the account with a team email
  2. Grant it organizer access to relevant groups
  3. Register the OAuth app under this account
  4. Store credentials in team password manager

This centralizes management and doesn’t depend on individual team members’ accounts.

Option 3: Environment-Specific Apps

Register separate OAuth apps for different environments:

  • Development: Individual developer credentials
  • Staging: Shared staging app
  • Production: Production-only app with restricted access

Use environment variables to switch between them:

# In .Renviron for each environment
MEETUP_CLIENT_ID=dev_client_id
MEETUP_CLIENT_SECRET=dev_secret

Credential Security Best Practices

Do’s

  • Store credentials in keyring or environment variables
  • Add .Renviron to .gitignore
  • Use CI platform’s secrets management
  • Rotate tokens periodically
  • Use separate OAuth apps for dev/staging/production
  • Limit scope to minimum required permissions

Don’ts

  • Never commit credentials to version control
  • Don’t share client secrets in plain text (Slack, email, etc.)
  • Don’t print secrets in logs or console output
  • Don’t use production credentials in development
  • Don’t share OAuth tokens between applications

Credential Rotation

To rotate compromised credentials:

# 1. Delete old credentials from keyring
meetup_key_delete("client_id")
meetup_key_delete("client_secret")
meetup_key_delete("token")
meetup_key_delete("token_file")

# 2. Revoke the OAuth app on Meetup.com
# Go to https://www.meetup.com/api/oauth/list/
# Click "Revoke" on the compromised app

# 3. Create a new OAuth app (or regenerate secret)

# 4. Store new credentials
meetup_key_set("client_id", "new_client_id")
meetup_key_set("client_secret", "new_secret")

# 5. Re-authenticate
meetup_auth()

# 6. Update CI secrets
meetup_ci_setup()

Audit Trail

Monitor your OAuth app usage:

  1. Visit https://www.meetup.com/api/oauth/list/
  2. Click on your app name
  3. View analytics and recent API usage

This helps detect unauthorized usage or unusual patterns.

Debugging Authentication Issues

Common Problems

Problem: meetup_auth() fails with “invalid_client”

Solution: Verify client ID and secret are correct:

# Check stored values
client_id <- meetup_key_get("client_id")
print(client_id) # Should match OAuth app key

# Re-enter credentials if incorrect
meetup_key_set("client_id", "correct_client_id")
meetup_key_set("client_secret", "correct_secret")

Problem: Token cached for wrong OAuth app

Solution: Clear cache and re-authenticate:

# Clear all authentication
meetup_deauth()

# Remove cached tokens
token_dir <- httr2::oauth_cache_path()
list.files(file.path(token_dir, "meetupr"))

# Delete specific token file if needed
# file.remove(file.path(
#   token_dir,
#   "meetupr",
#   "token.rds.enc"
# ))

# Re-authenticate
meetup_auth()

Problem: Multiple tokens warning

Solution: This happens when you’ve authenticated with different OAuth apps:

# See all cached tokens
token_dir <- httr2::oauth_cache_path()
list.files(file.path(token_dir, "meetupr"), pattern = "token.rds.enc$")

# Remove all tokens and start fresh
meetup_deauth()
meetup_auth()

Problem: CI authentication fails

Solution: Verify environment variables are set correctly:

# Check if env vars are present
Sys.getenv("meetupr:token") # Should be long base64 string
Sys.getenv("meetupr:token_file") # Should be filename

# If empty, check CI platform secrets configuration
# Ensure quotes around variable names in YAML: "meetupr:token"

Enable Debug Mode

For detailed authentication diagnostics:

# Enable debug mode
Sys.setenv(MEETUPR_DEBUG = "1")

# Run authentication
meetup_sitrep()
meetup_auth()

# Disable debug mode
Sys.setenv(MEETUPR_DEBUG = "0")

Debug output shows:

  • Credential lookup paths (keyring vs env vars)
  • OAuth token cache location
  • Request/response details

Inspect Token State

# Check authentication status
status <- meetup_auth_status()
print(status)

# Verify which credentials are being used
meetup_sitrep()

Environment Variables Reference

Authentication Credentials

Variable Purpose Example
MEETUP_CLIENT_ID OAuth app client ID ABC123XYZ
MEETUP_CLIENT_SECRET OAuth app client secret secret_key_here
MEETUP_CLIENT_NAME Client name for token cache my_app

CI/CD Mode

Variable Purpose Example
meetupr:token Base64-encoded OAuth token eyJhbGc...
meetupr:token_file Token cache filename ae743e0-token.rds.enc

Configuration

Variable Purpose Default Example
MEETUP_API_URL API base URL (for testing) https://api.meetup.com/gql-ext Custom URL
MEETUPR_DEBUG Enable debug logging 0 (off) 1 (on)

Note: Variables with colons (meetupr:token) must be quoted in YAML files.

Keyring Functions Reference

Storing Credentials

# Store with explicit value
meetup_key_set("client_id", "your_value")

# Store with interactive prompt
meetup_key_set("client_secret")

# Valid key names: "client_id", "client_secret", "token", "token_file"

Retrieving Credentials

# Get credential (errors if not found)
value <- meetup_key_get("client_id")

# Get credential (returns NULL if not found)
value <- meetup_key_get("client_id", error = FALSE)

Checking Availability

# Check if key exists
if (key_available("client_id")) {
  # Credential is stored
}

Deleting Credentials

# Remove specific credential
meetup_key_delete("client_id")

# Or delete all with deauth
meetup_deauth()

Production Deployment Checklist

Before deploying meetupr in production:

Complete CI/CD Example

Here’s a complete workflow for automated group analytics:

name: Weekly R-Ladies Analytics

on:
  schedule:
    - cron: '0 8 * * 1'  # Monday at 8 AM UTC
  workflow_dispatch:

jobs:
  analyze:
    runs-on: ubuntu-latest
    
    env:
      "meetupr:token": ${{ secrets.MEETUPR_TOKEN }}
      "meetupr:token_file": ${{ secrets.MEETUPR_TOKEN_FILE }}
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      
      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: 'release'
      
      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y libcurl4-openssl-dev libssl-dev
      
      - name: Install R packages
        run: |
          install.packages(c("remotes", "dplyr", "ggplot2"))
          remotes::install_github("rladies/meetupr")
        shell: Rscript {0}
      
      - name: Load CI token
        run: |
          library(meetupr)
          meetup_ci_load()
          meetup_sitrep()
        shell: Rscript {0}
      
      - name: Fetch data
        run: |
          library(meetupr)
          library(dplyr)
          
          # Get all R-Ladies groups
          groups <- get_pro_groups("rladies")
          saveRDS(groups, "data/groups.rds")
          
          # Get recent events
          events <- get_pro_events("rladies", "past", max_results = 100)
          saveRDS(events, "data/events.rds")
          
          # Summary stats
          summary <- events |>
            group_by(group_urlname) |>
            summarise(
              total_events = n(),
              avg_attendance = mean(going, na.rm = TRUE),
              .groups = "drop"
            )
          
          write.csv(summary, "reports/summary.csv", row.names = FALSE)
        shell: Rscript {0}
      
      - name: Generate report
        run: |
          library(rmarkdown)
          rmarkdown::render("reports/weekly_report.Rmd")
        shell: Rscript {0}
      
      - name: Commit results
        run: |
          git config --local user.name "GitHub Actions"
          git config --local user.email "actions@github.com"
          git add data/ reports/
          git diff --quiet && git diff --staged --quiet || git commit -m "Update weekly analytics"
          git push

This workflow:

  1. Runs weekly on a schedule
  2. Loads CI credentials from secrets
  3. Fetches Pro group and event data
  4. Generates summary statistics
  5. Renders a report
  6. Commits results back to repository

Additional Resources

Official Documentation

Security Resources

Getting Help

For authentication issues:

  1. Run diagnostics: meetup_sitrep() shows authentication status
  2. Enable debug mode: Sys.setenv(MEETUPR_DEBUG = "1")
  3. Check keyring: Verify credentials with key_available()
  4. Clear and retry: meetup_deauth() then meetup_auth()

When reporting issues, include:

  • Output from meetup_sitrep()
  • Whether you’re using custom credentials
  • Authentication method (interactive vs CI)
  • Platform (macOS, Windows, Linux)
  • Never include actual tokens or secrets

For package updates and issues, visit the GitHub repository.