---
source_hash: "b90852ec"
title: "SmartAuth"
weight: 10
description: "SmartAuth is the JWT authentication module for Dolibarr, designed for SmartMaker applications."
category: "Briques techniques"
type: "librairie"
---

# SmartAuth

SmartAuth is the JWT authentication module for Dolibarr, designed for SmartMaker applications.

## Why SmartAuth?

Natively, Dolibarr offers an API where each user has a single API key granting access to their whole functional scope.

This is a problem: if you develop a mobile application that should only have access to the user's agenda, the native key also lets the application reach the invoices and everything else.

### Our approach

With SmartAuth, a user can have **as many API keys as they want**, each key with its own permissions. If a key is tied to an application, it cannot be reused by another one.

Benefits:
- **Access partitioning**: each application has its own key
- **Targeted revocation**: if a device is stolen, delete only its key
- **Traceability**: connection logs per device

![List of SmartAuth access tokens: application, creation and last connection dates, state](images/20250903-082553.png)

### Connection logs

Open the logs to spot incorrect actions and manage the accesses:

![Connection log: third party, IP address, method, status code and called URL](images/20250903-082923.png)

## Installation

Download SmartAuth for free on the DoliStore: https://www.dolistore.com/product.php?id=2509&l=fr

## JWT authentication flow

### Overall diagram

```
┌─────────────┐         ┌─────────────┐         ┌─────────────┐
│   Mobile    │         │   api.php   │         │  Dolibarr   │
│   (React)   │         │   (JWT)     │         │   (PHP)     │
└──────┬──────┘         └──────┬──────┘         └──────┬──────┘
       │                       │                       │
       │  1. POST /login       │                       │
       │  {login, password}    │                       │
       │──────────────────────>│                       │
       │                       │  2. Check user        │
       │                       │──────────────────────>│
       │                       │                       │
       │                       │  3. User valid        │
       │                       │<──────────────────────│
       │  4. {accessToken,     │                       │
       │      refreshToken}    │                       │
       │<──────────────────────│                       │
       │                       │                       │
       │  5. GET /items        │                       │
       │  Authorization: Bearer│                       │
       │──────────────────────>│                       │
       │                       │  6. Validate token    │
       │                       │  + load user          │
       │                       │──────────────────────>│
       │                       │                       │
       │  7. Data              │                       │
       │<──────────────────────│                       │
```

### JWT tokens

SmartAuth uses two tokens:

| Token | Lifetime | Usage |
| --- | --- | --- |
| `accessToken` | 15 minutes | Authenticate the API requests |
| `refreshToken` | 7 days | Obtain a new accessToken |

### Authentication endpoints

| Method | Route | Description | Protected |
| --- | --- | --- | --- |
| GET | `/login` | Fetch the connection info (logo, etc.) | No |
| POST | `/login` | Authentication (login + password) | No |
| GET | `/refresh` | Renew the accessToken | No |
| POST | `/logout` | Sign out (invalidates the refreshToken) | Yes |
| POST | `/device` | Register a device for the notifications | Yes |
| POST | `/qr-pair/{pairingId}/claim` | Mobile claims a pairing displayed by the PC | No |
| POST | `/qr-pair/{pairingId}/poll` | Mobile polls until the PC confirms | No |

## Configuration on the api.php side

### Authentication routes

```
<?php
require_once '../smartmaker-api-prepend.php';

use SmartAuth\Api\AuthController;
use SmartAuth\Api\RouteController as Route;

// Public routes
Route::get('login',     AuthController::class, 'index');      // Connection info
Route::post('login',    AuthController::class, 'login');      // Authentication
Route::get('refresh',   AuthController::class, 'refresh');    // Refresh token

// Protected routes
Route::post('logout',   AuthController::class, 'logout', true);  // Sign out
Route::post('device',   AuthController::class, 'device', true);  // Register device

// Your business routes...
Route::get('items', ItemController::class, 'index', true);

// Fallback
json_reply('Access denied', 403);
```

### The smartmaker-api-prepend.php file

This file (generated by SmartBoot) contains:
- The mandatory Dolibarr headers
- The loading of the SmartAuth autoloader
- The initialization of the JWT layer
- The autoloader for your module classes

> [!IMPORTANT]
> Do not modify this file unless you know what you are doing.

## Usage on the React side

### Provider configuration

```
// appConfig.js
export const config = {
  api: {
    prefixUrl: import.meta.env.VITE_API_URL,
    paths: {
      login: "login",
      logout: "logout",
      refresh: "refresh",
    },
  },
};
```

### Login with useApi (manual)

```
import { useApi, useGlobalStates } from '@cap-rel/smartcommon';

const LoginPage = () => {
  const api = useApi();
  const gst = useGlobalStates();

  const handleLogin = async (data) => {
    try {
      const user = await api.login(data);
      gst.local.set('session', user);
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit(handleLogin)}>
      {/* ... */}
    </form>
  );
};
```

### Simplified login with LoginComponent

Rather than reinventing the form, smartcommon exposes `<LoginComponent>`, which wraps the complete flow: email + password + optional entity selection + **smartAuth QR pair scan**:

```
import { LoginComponent } from '@cap-rel/smartcommon';

<LoginComponent
  onSuccess={(user) => navigate("/")}
  onError={(err) => log.error(err)}
  // QR pair enabled by default. Disable it explicitly when the backend
  // does not expose /qr-pair (non-smartAuth case).
  enableQrPair
  showRememberMe
  labels={{
    emailLabel: t("login.email"),
    submitLabel: t("login.submit"),
    scanQrLabel: t("login.scan-qr"),
  }}
/>
```

See [Advanced components -> LoginComponent](/front/composants-avances#logincomponent) for the labels, styling slots and options of the QR pair flow.

### Automatic refresh

The `useApi` hook handles the token refresh automatically:

```
// When the accessToken expires, useApi:
// 1. Intercepts the 401 error
// 2. Calls GET /refresh with the refreshToken
// 3. Updates the tokens in the session
// 4. Replays the original request
```

### Logout

```
const handleLogout = async () => {
  await api.private.post('logout');
  setSession(null);
  navigate('/login');
};
```

## Device management

### Registering a device

For push notifications, register the device after the login:

```
const registerDevice = async (pushToken) => {
  await api.private.post('device', {
    json: {
      token: pushToken,
      platform: 'android', // or 'ios', 'web'
      name: 'My phone',
    },
  });
};
```

### Structure on the Dolibarr side

SmartAuth stores the devices in a dedicated table:

| Field | Description |
| --- | --- |
| `fk_user` | ID of the Dolibarr user |
| `token` | Push token of the device |
| `platform` | Platform (android, ios, web) |
| `name` | Device name |
| `last_used` | Date of last use |

### Device identification (DeviceIdentificationComponent)

When a user signs in from a device that is not registered yet, SmartAuth can return a `user.deviceOptions` listing their existing devices so they can choose to pair or to create a new one.

The `<DeviceIdentificationComponent>` component wraps that flow:

```
import { DeviceIdentificationComponent } from '@cap-rel/smartcommon';

<DeviceIdentificationComponent
  onSuccess={() => navigate("/")}
  onError={(err) => toast.error(err.message)}
  labels={{ title: t("device.title"), submitLabel: t("device.submit") }}
/>
```

On submit, `api.identifyDevice({ label, uuid })` is called. The smartAuth endpoint clears `user.deviceOptions` on the server side. See [details](/front/composants-avances#deviceidentificationcomponent).

## QR pair (login by scanning)

SmartAuth lets a user sign in on mobile by scanning a QR code displayed by an already authenticated workstation, without typing a password. The mobile becomes a "trusted" device persisted locally.

### Endpoints

```
POST /qr-pair/{pairingId}/claim   // Mobile claims the pairing
POST /qr-pair/{pairingId}/poll    // Mobile polls until confirmation
```

### On the useApi side

`useApi()` exposes two matching methods:

```
const api = useApi();

// 1. Mobile claims a pairing_id displayed by the PC
const { claim_token } = await api.claimQrPair(pairingId, {
  device_label: "iPhone Eric",
  device_uuid: "u-1",
});

// 2. Mobile polls until the PC confirms
//    Statuses: 'pending' | 'cancelled' | 'expired' | 'consumed'
const data = await api.pollQrPair(pairingId, claim_token);
// data.status === 'consumed': user persisted automatically in local storage
```

When `pollQrPair` returns `consumed`, the user is **persisted automatically** (by design: a scanned device is trusted). There is no auth helper to call.

### Built-in UX: LoginComponent

So that this flow does not have to be reimplemented in every app, `<LoginComponent enableQrPair />` (enabled by default) handles scan -> claim -> poll with:
- An idempotency guard (avoids the 409 on Android with autofocus)
- A full-screen overlay with a spinner during claim/poll
- A "Cancel" button and a global timeout
- Customisable error mapping through `getQrErrorLabel`

See [details](/front/composants-avances#logincomponent).

### Backend reference

`~/dev/smartauth/api/QrPairController.php`. Error codes exposed through `error.apiCode`:
- `pairing_not_claimable` / 409: QR already used
- `pairing_not_found` / 404: invalid QR
- `pairing_expired` / 410: expired QR
- `rate_limited` / 429: too many attempts
- `invalid_pairing_id` / 400: incorrect format

## Security

### Best practices
- **HTTPS mandatory**: the JWT tokens travel in clear text
- **Secure storage**: use `useGlobalStates`, which persists in encrypted localStorage
- **Token rotation**: the refreshToken is single use
- **Revocation**: delete the keys from the Dolibarr interface

### If a device is stolen

1. Sign in to Dolibarr
2. Go to your user profile
3. Delete the API key of the stolen device
4. The associated tokens are invalidated immediately

## Password reset

SmartAuth includes a complete token-based password reset system.

### Endpoints

| Method | Route | Description | Protected |
| --- | --- | --- | --- |
| POST | `/password/reset` | Reset request (sends an email) | No |
| POST | `/password/confirm` | Confirmation with a token | No |
| POST | `/password/change` | Password change (signed-in user) | Yes |

### Requesting a reset

```
// React side
const requestReset = async (email) => {
  await api.public.post('password/reset', {
    json: { email }
  });
};
```

The server:

- Checks the user (rate limiting: 3 attempts per 15 minutes)
- Generates a token with an expiry (1 hour)
- Sends an email with the reset link

### Confirming the reset

```
const confirmReset = async (token, newPassword) => {
  await api.public.post('password/confirm', {
    json: { token, password: newPassword }
  });
};
```

### Changing the password (signed-in user)

```
const changePassword = async (oldPassword, newPassword) => {
  await api.private.post('password/change', {
    json: {
      current_password: oldPassword,
      new_password: newPassword
    }
  });
};
```

## Temporary files (SmartTempFile)

SmartAuth provides a temporary file system for downloading binaries (Excel exports, generated PDFs, etc.).

### Endpoints

| Method | Route | Description |
| --- | --- | --- |
| GET | `/temp-file/{token}` | base64 download (JSON) |
| GET | `/temp-file/{token}/binary` | Binary download (stream) |
| DELETE | `/temp-file/{token}` | File deletion |

### Usage on the PHP side (Controller)

```
use SmartAuth\Api\SmartTempFile;

public function exportExcel($payload)
{
    // Generate the file
    $filePath = '/tmp/export.xlsx';
    // ... file generation ...

    // Store as a temporary file (default TTL: 1 hour)
    $tempFile = new SmartTempFile();
    $token = $tempFile->store($filePath, [
        'filename' => 'export.xlsx',
        'mimetype' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'user_id' => $payload['user_id'],
        'entity' => $payload['entity'],
    ]);

    return [['token' => $token], 200];
}
```

### Usage on the React side

```
// Binary download
const downloadExport = async (token) => {
  const response = await api.get(`temp-file/${token}/binary`, { raw: true });
  const blob = await response.blob();

  // Create a download link
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'export.xlsx';
  a.click();
  URL.revokeObjectURL(url);
};
```

## CORS

SmartAuth handles the CORS headers for cross-origin requests automatically.

### Configuration

| Dolibarr constant | Description | Default |
| --- | --- | --- |
| `SMARTAUTH_CORS_ORIGIN` | Allowed origin | `*` |
| `SMARTAUTH_CORS_METHODS` | Allowed methods | `GET, POST, PUT, DELETE, PATCH, OPTIONS` |
| `SMARTAUTH_CORS_HEADERS` | Allowed headers | `Content-Type, Authorization, X-DeviceId` |

The OPTIONS (preflight) requests are handled automatically.

> [!IMPORTANT]
> In production, set `SMARTAUTH_CORS_ORIGIN` to the exact URL of your application instead of `*`.

## Trusted proxies

If your Dolibarr sits behind a reverse proxy (nginx, Apache, etc.), configure the trusted IPs so that SmartAuth detects the client IP address correctly:

| Constant | Description |
| --- | --- |
| `SMARTAUTH_TRUSTED_PROXIES` | Comma-separated list of IPs (e.g. `10.0.0.1,192.168.1.100`) |

Private IPs (127.x, 10.x, 172.16-31.x, 192.168.x) are automatically treated as trusted proxies.

## Local routes (LocalRoutes)

SmartAuth defines local routes that are loaded automatically. They do not need to be declared in your `api.php`:

| Group | Routes |
| --- | --- |
| Auth | `/login`, `/refresh`, `/logout`, `/device` |
| Password | `/password/reset`, `/password/confirm`, `/password/change` |
| Files | `/file/{hash}`, `/file/{hash}/binary` |
| Temporary files | `/temp-file/{token}`, `/temp-file/{token}/binary` |
| Synchronization | `/sync/register`, `/sync/pull`, `/sync/push`, `/sync/status`, `/sync/conflicts` |
| PWA | `/manifest`, `/icon/{size}` |

## See also
- [Back (PHP)](/back) - Routes and Controllers
- [API requests](/front/requetes-api) - Using useApi
- [Configuration](/front/configuration) - Provider configuration
- SmartAuth repository: https://inligit.fr/cap-rel/dolibarr/plugin-smartauth/
