> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dhmad.tn/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Setup

> Configure OAuth 2.0 for SSO integration

DHMAD supports OAuth 2.0 for Single Sign-On (SSO) integration, allowing users to authenticate with their DHMAD accounts in your application.

## Overview

OAuth 2.0 allows your application to:

* Authenticate users with their DHMAD accounts
* Access user information (with permission)
* Provide seamless SSO experience

## Getting Your OAuth Credentials

1. Log into the [Developer Dashboard](https://developer.dhmad.tn/dashboard)
2. Navigate to the "OAuth Client Credentials" section
3. Copy your **Client ID** and **Client Secret**

<Warning>
  Keep your Client Secret secure! Never expose it in client-side code or commit it to version control.
</Warning>

## Configuring Redirect URIs

Redirect URIs must be full URLs including a path. During authorization, the `redirect_uri` parameter must exactly match one of your registered URIs.

```
https://example.com/callback
https://example.com/auth/dhmad/callback
```

<Warning>
  Domain-only URIs (e.g., `https://example.com`) are **not accepted**. You must include a path such as `/callback` or `/auth/callback`. Each redirect URI is matched exactly — no wildcard or pattern matching is performed.
</Warning>

## OAuth Flow

The OAuth 2.0 authorization code flow with PKCE:

1. **Authorization Request** - Redirect user to authorization endpoint (the `state` parameter is **required**)
2. **Consent Screen** - User sees which application is requesting access and what permissions (scopes) are being requested
3. **User Authentication** - User logs in with their DHMAD account
4. **Authorization** - User authorizes the application
5. **Authorization Code** - User is redirected back with code and state
6. **Token Exchange** - Exchange code for access token
7. **User Info** - Use access token to get user information

<Warning>
  The `state` parameter is **required**. Requests without a `state` parameter will be rejected with an `invalid_request` error. Generate a unique, random value for each authorization request and validate it on the callback to prevent CSRF attacks.
</Warning>

## Implementation Example

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Step 1: Generate PKCE challenge and state
  import crypto from 'crypto';

  function generatePKCE() {
    const codeVerifier = crypto.randomBytes(32).toString('base64url');
    const codeChallenge = crypto
      .createHash('sha256')
      .update(codeVerifier)
      .digest('base64url');
    
    return { codeVerifier, codeChallenge };
  }

  // Step 2: Redirect to authorization
  const { codeVerifier, codeChallenge } = generatePKCE();
  sessionStorage.setItem('code_verifier', codeVerifier);

  // state is REQUIRED — generate a random value and store it for validation
  const state = crypto.randomBytes(16).toString('hex');
  sessionStorage.setItem('oauth_state', state);

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

  window.location.href = `https://dhmad.tn/api/oauth/authorize?${params}`;

  // Step 3: Handle callback — validate state before using the code
  const urlParams = new URLSearchParams(window.location.search);
  const code = urlParams.get('code');
  const returnedState = urlParams.get('state');

  if (returnedState !== sessionStorage.getItem('oauth_state')) {
    throw new Error('Invalid state parameter — possible CSRF attack');
  }

  // Step 4: Exchange code for token
  const tokenResponse = await fetch('https://dhmad.tn/api/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code_verifier: sessionStorage.getItem('code_verifier')
    })
  });

  const tokens = await tokenResponse.json();

  // Step 5: Get user info
  const userResponse = await fetch('https://dhmad.tn/api/oauth/userinfo', {
    headers: {
      'Authorization': `Bearer ${tokens.access_token}`
    }
  });

  const user = await userResponse.json();
  ```

  ```python Python theme={null}
  import secrets
  import hashlib
  import base64
  import requests

  # Step 1: Generate PKCE
  def generate_pkce():
      code_verifier = base64.urlsafe_b64encode(
          secrets.token_bytes(32)
      ).decode('utf-8').rstrip('=')
      
      code_challenge = base64.urlsafe_b64encode(
          hashlib.sha256(code_verifier.encode()).digest()
      ).decode('utf-8').rstrip('=')
      
      return code_verifier, code_challenge

  # Step 2: Authorization URL
  code_verifier, code_challenge = generate_pkce()
  session['code_verifier'] = code_verifier

  # state is REQUIRED — generate a random value and store it for validation
  state = secrets.token_urlsafe(16)
  session['oauth_state'] = state

  auth_url = 'https://dhmad.tn/api/oauth/authorize'
  params = {
      'client_id': CLIENT_ID,
      'redirect_uri': REDIRECT_URI,
      'response_type': 'code',
      'scope': 'openid profile email',
      'code_challenge': code_challenge,
      'code_challenge_method': 'S256',
      'state': state
  }

  # Redirect user to: f"{auth_url}?{urlencode(params)}"

  # Step 3: Handle callback — validate state before using the code
  if request.args.get('state') != session.get('oauth_state'):
      raise ValueError('Invalid state parameter')

  code = request.args.get('code')
  token_response = requests.post(
      'https://dhmad.tn/api/oauth/token',
      json={
          'grant_type': 'authorization_code',
          'code': code,
          'redirect_uri': REDIRECT_URI,
          'client_id': CLIENT_ID,
          'client_secret': CLIENT_SECRET,
          'code_verifier': session['code_verifier']
      }
  )

  tokens = token_response.json()

  # Step 4: Get user info
  user_response = requests.get(
      'https://dhmad.tn/api/oauth/userinfo',
      headers={'Authorization': f"Bearer {tokens['access_token']}"}
  )

  user = user_response.json()
  ```
</CodeGroup>

## Scopes

Available OAuth scopes:

* `openid` - OpenID Connect (required for ID token)
* `profile` - User profile information
* `email` - User email address

## Security Best Practices

<Warning>
  DHMAD only supports the `S256` code challenge method for PKCE. The `plain` method is **not** accepted and will be rejected with an `invalid_request` error. Always use SHA-256 to hash your code verifier.
</Warning>

<CardGroup cols={2}>
  <Card title="Use PKCE (S256)" icon="shield-check">
    Always use PKCE with the S256 code challenge method (plain is not supported)
  </Card>

  <Card title="HTTPS Only" icon="lock">
    Always use HTTPS for redirect URIs in production
  </Card>

  <Card title="State is Required" icon="check">
    The `state` parameter is mandatory. Requests without it are rejected. Generate a unique random value per request and validate it on callback to prevent CSRF attacks.
  </Card>

  <Card title="Secure Storage" icon="key">
    Store tokens securely, never in localStorage for sensitive apps
  </Card>
</CardGroup>

## Consent Screen

When a user is redirected to the authorization endpoint, they are shown a consent screen that displays:

* **Your application name** (as registered in the Developer Dashboard)
* **Requested permissions** (scopes) with clear descriptions of what data will be shared

The user must explicitly authorize the request before an authorization code is issued. If the user clicks "Cancel", they are redirected back to your `redirect_uri` with `error=access_denied`.

## Client Info Endpoint

You can retrieve public information about an OAuth client (useful for pre-flight checks):

```bash theme={null}
curl https://dhmad.tn/api/oauth/client-info?client_id=YOUR_CLIENT_ID
```

**Response:**

```json theme={null}
{
  "name": "Your Application Name",
  "scopes": ["openid", "profile", "email"]
}
```

This endpoint is public and does not require authentication. It returns only the client name and allowed scopes.

## Discovery Endpoint

Get OAuth configuration:

```bash theme={null}
curl https://dhmad.tn/.well-known/openid-configuration
```

## Support

For OAuth integration help, contact [support@dhmad.tn](mailto:support@dhmad.tn)

***

<Info>
  OAuth credentials are automatically generated when you first access your developer dashboard. You can regenerate your client secret at any time.
</Info>
