Developer Documentation

Everything you need to integrate rIDP into your applications. rIDP is a self-hosted OAuth 2.0 authentication server designed for simplicity and privacy.


Getting Started

1. Create an Application

First, create a new application in the developer portal to get your OAuth credentials. Navigate to Applications and click "Create New".

2. Get Your Credentials

After creating your application, you'll receive:

  • Client ID — Public identifier for your application
  • Client Secret — Keep this confidential! Never expose it in client-side code.

3. Configure Your Redirect URI

When creating your application, you must specify the redirect URI(s) where users will be sent after authentication. This must exactly match the URI you use in your OAuth requests.

4. Available Scopes

Request the following scopes to access user information:

ScopeDescription
openidReturns the user's unique identifier (sub).
profileAccess to user's name and profile information.
emailAccess to user's email address.

5. Integrate in Your Application

Add a "Login with rIDP" button that redirects users to the authorization endpoint:

const authUrl = `https://your-idp.com/oauth/authorize?` +
  `response_type=code&` +
  `client_id=YOUR_CLIENT_ID&` +
  `redirect_uri=${encodeURIComponent('https://yourapp.com/callback')}&` +
  `scope=openid profile email&` +
  `state=${generateRandomState()}`;

window.location.href = authUrl;

OAuth 2.0 Authorization Flow

Authorization Code Flow

The most secure flow for web applications with server-side code.

  1. Redirect to Authorization Endpoint
    GET /oauth/authorize
  2. User Authenticates and Consents

    User logs in and approves your application's access request.

  3. Receive Authorization Code

    User is redirected back to your callback URL with a temporary code.

  4. Exchange Code for Tokens
    POST /oauth/token
  5. Use Access Token

    Make API calls (e.g., to UserInfo) with the access token.

Security Note: Never expose your Client Secret in client-side code. For Single Page Applications, use PKCE (Proof Key for Code Exchange).


API Reference

GET /oauth/authorize

Initiates the OAuth 2.0 authorization flow.

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour application's Client ID
redirect_uriYesRegistered callback URL
scopeNoSpace-separated list of scopes (e.g., openid profile email)
stateYesCSRF protection token (unguessable random string)
resourceNoResource Indicators per RFC 8707 (array of URIs)
POST /oauth/token

Exchange authorization code for access tokens, or refresh an existing token.

Content-Type: Requests must use application/x-www-form-urlencoded

Supported Grant Types

  • authorization_code — Exchange authorization code for tokens
  • refresh_token — Get new access token using refresh token
ParameterRequiredDescription
grant_typeYesauthorization_code or refresh_token
client_idYesYour application's Client ID
client_secretConditionalRequired for confidential clients
codeConditionalRequired for authorization_code grant
redirect_uriConditionalRequired for authorization_code grant (must match initial request)
refresh_tokenConditionalRequired for refresh_token grant
scopeNoSpace-separated scopes (must be subset of granted scopes)

Response

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...",
  "scope": "openid profile email"
}
GET /oauth/userinfo

Retrieve information about the authenticated user.

Headers

HeaderRequiredDescription
AuthorizationYesBearer {access_token} (must have "openid" scope)

Response (200 OK)

{
  "sub": "6d9e1f2a-3e3b-4eae-bf8a-9f8b6de51577",
  "email": "user@example.com"
}

Error Responses

StatusErrorDescription
401invalid_tokenAccess token is missing, invalid, or expired
403insufficient_scopeAccess token lacks required "openid" scope

Self-Hosting

The easiest way to run rIDP is using Docker Compose. Add the following service to your docker-compose.yml file:

version: '3.8'

services:
  ridp:
    image: ridp/server:latest
    ports:
      - "8000:8000"
    environment:
      - SECRET_KEY=change-this-to-a-secure-random-string
      - ALLOWED_HOSTS=localhost,yourdomain.com
      - DB_NAME=ridp
      - DB_USER=ridp
      - DB_PASSWORD=securepassword
      - DB_HOST=db
    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=ridp
      - POSTGRES_USER=ridp
      - POSTGRES_PASSWORD=securepassword
    volumes:
      - ridp_data:/var/lib/postgresql/data

volumes:
  ridp_data:

Superuser Setup

After starting the container, you may need to create a superuser to access the admin panel. You can do this by executing commands directly in the running container.

List Users

docker-compose exec ridp python manage.py shell -c "from portal_auth.models import User; [print(f'{u.id} | {u.email}') for u in User.objects.all()]"

Promote User to Superuser

Replace YOUR_EMAIL with the email of the user you want to promote.

docker-compose exec ridp python manage.py shell -c "from portal_auth.models import User; u = User.objects.get(email='YOUR_EMAIL'); u.is_staff = True; u.is_superuser = True; u.save(); print('Promoted!')"

Code Examples

Express.js Complete Example

import express from 'express';
import session from 'express-session';
import crypto from 'crypto';

const app = express();

const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const REDIRECT_URI = 'https://yourapp.com/callback';
const IDP_BASE_URL = 'https://your-idp.com';

app.use(session({
  secret: 'your-session-secret',
  resave: false,
  saveUninitialized: false
}));

// Step 1: Redirect to rIDP login
app.get('/login', (req, res) => {
  const state = crypto.randomUUID();
  req.session.oauth_state = state;

  const params = new URLSearchParams({
    response_type: 'code',
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    scope: 'openid profile email',
    state: state
  });

  res.redirect(`${IDP_BASE_URL}/oauth/authorize?${params}`);
});

// Step 2: Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state matches
  if (state !== req.session.oauth_state) {
    return res.status(400).send('Invalid state');
  }

  // Exchange code for tokens
  const tokenResponse = await fetch(`${IDP_BASE_URL}/oauth/token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: REDIRECT_URI
    })
  });

  const tokens = await tokenResponse.json();

  // Get user info
  const userResponse = await fetch(`${IDP_BASE_URL}/oauth/userinfo`, {
    headers: {
      'Authorization': `Bearer ${tokens.access_token}`
    }
  });

  const user = await userResponse.json();
  req.session.user = user;
  res.redirect('/dashboard');
});