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

# Authentication Providers

> This article provides an introduction to Ditto's methods for server authentication.

Learn more about Ditto's authentication methods in [Authentication and
Authorization](/key-concepts/authentication-and-authorization).

This section will require knowledge of writing HTTP webhooks. This example is
written in JavaScript (NodeJS with an [Express](https://expressjs.com/ "Express")-like API), however you can use any framework or language of your
choosing.

The authentication webhook needs to handle an **HTTP POST** request. Each client
that will need to authenticate will send a payload to this webhook. The
following section requires that you have knowledge of writing server-side HTTP
endpoints and responding with a JSON payload.

## Example Webhook

You can use [this
example](https://github.com/getditto/sample-authentication-permissions/tree/main/server/simple)
webhook to test your application. However, you should use your own webhook in
production. The example simply authenticates all requests for 7 days of offline
usage.

## Building your Authentication Webhook

### Incoming POST body

​When your client device wants to authenticate using your webhook, your webhook will receive an HTTP post with a JSON payload that looks like:

<CodeGroup>
  ```json v1 theme={null}
  {
    "databaseID": "YOUR_DATABASE_ID_HERE", 
    "version": "1",
    "provider": "my-auth", // this is the "Name" of the "Authentication Webhook"
    "token": "eyJhbGciOiJI..." // this is what each device will send to authenticate
  }
  ```

  ```json legacy theme={null}
  {
    "appID": "YOUR_APP_ID_HERE", // the databaseID
    "provider": "my-auth", // this is the "Name" of the "Authentication Webhook"
    "token": "eyJhbGciOiJI..." // this is what each device will send to authenticate
  }
  ```
</CodeGroup>

Your server can introspect these values by parsing out the request body:

```js theme={null}
let express = require('express')
let cors = require('cors')
let body = require('body-parser')
let app = express()

app.use(cors())
app.use(body.json())

let app = express()

app.post('/', (req, res) => {
  const databaseID = req.body.databaseID // or appID in legacy
  const provider = req.body.provider
  const token = req.body.token
})
```

### Response body

Your webhook must respond with a JSON payload that tells Ditto Cloud whether to authenticate the client and, if so, what permissions to grant and for how long. A single response object carries both the authentication decision (`authenticated`) and the authorization detail (`permissions`). The full schema Ditto Cloud expects is below.

<Accordion title="JSON Schema">
  ```json theme={null}
  {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Webhook Response",
    "description": "This describes the JSON response Ditto Cloud expects from the authentication webhook receiver.",
    "type": "object",
    "required": [
      "authenticated"
    ],
    "properties": {
      "authenticated": {
        "description": "Should this request be authenticated?",
        "type": "boolean"
      },
      "clientInfo": {
        "description": "This is an optional arbitrary JSON blob that can be provided.  It will be passed through the system and provided to the client."
      },
      "expirationSeconds": {
        "description": "The amount of time in seconds in which the session should be valid.  This field is required if authenticated is true.",
        "type": [
          "integer",
          "null"
        ],
        "format": "uint32",
        "minimum": 0.0
      },
      "identityServiceMetadata": {
        "description": "This is an optional dictionary that can be provided which will be signed by the identity service and gossiped through the presence data between peers. Because it's stored and shared proactively between every peer on the mesh, it's important to keep this data as small as possible.",
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": true
      },
      "identityServiceSignedInfo": {
        "description": "DEPRECATED: Migrate to identityServiceMetadata.",
        "readOnly": true,
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": true
      },
      "permissions": {
        "description": "The permission set for the user for this session.  This field is required if authenticated is true.",
        "anyOf": [
          {
            "$ref": "#/definitions/Permission"
          },
          {
            "type": "null"
          }
        ]
      },
      "userID": {
        "description": "The users identity inside Ditto.  This field is required if authenticated is true.",
        "default": null,
        "type": [
          "string",
          "null"
        ]
      },
      "version": {
        "description": "The version of the webhook request that triggered this response. If the request contained version '1', this field will be set to '1'. If the request did not contain a version field, this field will be omitted.",
        "type": [
          "string",
          "null"
        ]
      }
    },
    "definitions": {
      "Permission": {
        "type": "object",
        "required": [
          "read",
          "write"
        ],
        "properties": {
          "qlVersion": {
            "description": "What query language version are the queries written in? If 1, use DQL. If 0, use legacy query builder. Other numbers are not valid. Defaults to 0.",
            "type": [
              "integer",
              "null"
            ],
            "format": "uint8",
            "minimum": 0.0
          },
          "read": {
            "description": "The set of read permission granted",
            "allOf": [
              {
                "$ref": "#/definitions/PermissionRules"
              }
            ]
          },
          "remoteQuery": {
            "description": "Can this initiate remote queries?  In addition to this, remote queries also require full read permission.  Defaults to false",
            "type": [
              "boolean",
              "null"
            ]
          },
          "write": {
            "description": "The set of write permission granted",
            "allOf": [
              {
                "$ref": "#/definitions/PermissionRules"
              }
            ]
          }
        }
      },
      "PermissionRules": {
        "type": "object",
        "required": [
          "everything",
          "queriesByCollection"
        ],
        "properties": {
          "everything": {
            "description": "Does the user have unlimited permissions of this type?",
            "type": "boolean"
          },
          "queriesByCollection": {
            "description": "If 'everything' is set false then this contains a list of rules that define what the entity may access.  The key is the collection and the value is a set of queries.",
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
  ```
</Accordion>

Generally, you will want to check the token for some sort of validity. Let's
assume you have some sort of library or logic to parse and validate the token is
for a specific user. You can also use the clientInfo key in your JSON response
to pass information back to the client.

```js v1 theme={null}
app.post('/', async (req, res) => {
  const token = req.body.token;
  try {
    // The token that your webhook receives from ditto is always a string
    let parsedToken = JSON.parse(token)
    let payload = getDittoPermissions(parsedToken)
    res.json(payload)
  } catch (err) {
    res.json({
      "authenticated": err,
      "clientInfo": err.message
    })
  }
})
```

As a simple example, let's grant full read & write permissions to all collections and all documents.

<Note>
  Include `"version": "1"` in your response payload. It is required for DQL query-based permissions (`queriesByCollection`). Responses that omit `version` are treated as legacy-format responses and will not evaluate DQL permission queries.
</Note>

```js JS theme={null}
app.post('/', async (req, res) => {
  const token = req.body.token;
  try {
    let payload = {
      "authenticated": true,
      "expirationSeconds": 28800,
      "version": "1", // required for DQL query-based permissions
      "userID": "123abc",
      "permissions": {
        "read": {
          "everything": true,
          "queriesByCollection": {}
        },
        "write": {
          "everything": true,
          "queriesByCollection": {}
        }
      }
    }
    res.json(payload)
  } catch (err) {
    res.json({
      "authenticated": err,
      "clientInfo": err.message
    })
  }
})
```

For more information on how to design your app's permissions, see [Data Authorization](./data-authorization).

### Receiving the token in a request header (BYOC only)

By default, Ditto sends the authentication `token` inside the JSON request body, as
shown in [Incoming POST body](#incoming-post-body).

Users with a BYOC (Bring Your Own Cloud) Ditto Cloud deployment can instead request
that Ditto send the token in an **HTTP request header**, with a name of your choosing.

This may be useful to fit an existing API gateway or reverse-proxy convention that expects the credential in a header.

Because it is applied at the Ditto Server level, it affects every authentication webhook provider and every app deployed on that Ditto Server.

To enable this, [contact Ditto support](https://support.ditto.com).

When this is enabled:

* Ditto sends the token as the value of an HTTP header whose **name (key) you
  choose**.
* The `token` field is **omitted from the JSON body**.

For example, if you choose the header key `X-Auth-Token`, your webhook receives:

```http theme={null}
POST / HTTP/1.1
Content-Type: application/json
X-Auth-Token: eyJhbGciOiJI...

{
  "databaseID": "YOUR_DATABASE_ID_HERE",
  "version": "1",
  "provider": "my-auth"
}
```

Read the token from the header rather than the body:

```js theme={null}
app.post('/', async (req, res) => {
  const token = req.get('X-Auth-Token'); // instead of req.body.token
  // ...validate the token and return permissions as shown above
})
```

### Deploy your webhook

​Now, deploy your webhook. The portal will attempt to reach this webhook. That
means you must deploy it somewhere that this HTTP request is accessible.

<Info>
  Please be sure that this endpoint is not behind a firewall or VPN. If you cannot get around this requirement [contact us](https://support.ditto.com).
</Info>

## Declare your Webhook as a Ditto Authentication Provider

​To enable Authentication, you need to declare your deployed webhook as a Ditto Authentication Provider through the Ditto Portal.

Open your database in the [portal](https://portal.ditto.live/ "portal") and find
the **Authentication Mode & Webhook Settings** section. Ensure
that **"Authentication"** is turned on:

<Frame>
  <img src="https://mintcdn.com/ditto-248bc0d1/_UNdP98-Q-K7lTyJ/images/v4.9/authentication-2.webp?fit=max&auto=format&n=_UNdP98-Q-K7lTyJ&q=85&s=4347e6efcd05ed21b663addaa54871f8" width="600" height="204" data-path="images/v4.9/authentication-2.webp" />
</Frame>

Below, a section called **Authentication Providers** will be editable. Once your
Webhook Endpoint is deployed and ready, you can add **Name** and **URL**.

* **Name**: Provide a unique name for your webhook provider. This name will be used by the Ditto SDK to authenticate clients.
* **URL**: The URL is the fully qualified URL of the webhook that you deploy yourself starting with `https://`.

Once configured, you should see an authentication provider that looks like this in your portal database settings:

<Frame>
  <img src="https://mintcdn.com/ditto-248bc0d1/_UNdP98-Q-K7lTyJ/images/v4.9/authentication-3.webp?fit=max&auto=format&n=_UNdP98-Q-K7lTyJ&q=85&s=14b788bc941eacc52164b406e0b20ecc" width="600" height="64" data-path="images/v4.9/authentication-3.webp" />
</Frame>

## SDK Authentication

Once you have deployed your webhook and registered it as an authentication provider in the
portal, you can use the Ditto SDK to authenticate your clients.

In v5, authentication is configured using `DittoConfig` to connect to your server, and you set up an `expirationHandler` that is called when authentication credentials are about to expire. Within this handler, you call `ditto.auth.login(token, provider)` to refresh authentication.

Use the `provider` name you set in the portal and the `token` that your
authentication webhook expects. The `token` is typically a JWT or some other
authentication token that your webhook can validate.

<CodeGroup>
  ```swift Swift theme={null}
  // Your Ditto server URL
  let providerName = "YOUR_PROVIDER_NAME"

  let serverURL = URL(string: "REPLACE_ME_WITH_YOUR_URL")!
  let config = DittoConfig(
      databaseID: "REPLACE_ME_WITH_YOUR_DATABASE_ID", // This was "appID" in v4
      connect: .server(url: serverURL), // This was "Custom Auth URL" in v4
  )

  let ditto = try await Ditto.open(config: config)

  // Set up authentication expiration handler (required for server connections)
  ditto.auth?.expirationHandler = { [weak self] ditto, secondsRemaining in
      // Get token from your authentication system
      let token = await getMyToken()

      ditto.auth?.login(
          token: token,
          provider: providerName
      ) { clientInfo, error in
          if let error = error {
              print("Authentication failed: \(error)")
          } else {
              print("Authentication successful")
          }
      }
  }

  try ditto.sync.start()
  ```

  ```typescript JavaScript theme={null}
  // Your Ditto server URL
  const endpoint = 'REPLACE_ME_WITH_YOUR_URL';
  const databaseId = 'REPLACE_ME_WITH_YOUR_DATABASE_ID';
  const providerName = 'YOUR_PROVIDER_NAME';

  const config = new DittoConfig(
      databaseId, // This was "appID" in v4
      {
          type: 'server',
          url: endpoint
      }
  );

  const ditto = await Ditto.open(config);

  // Set up authentication expiration handler (required for server connections)
  await ditto.auth.setExpirationHandler(async (ditto, secondsRemaining) => {
      // Authenticate when token is expiring
      try {
          // Get token from your authentication system
          const token = await getMyToken();
          
          await ditto.auth.login(
              token,
              providerName
          );
          console.log('Authentication successful');
      } catch (error) {
          console.error('Authentication failed:', error);
      }
  });

  ditto.startSync();
  ```

  ```kotlin Kotlin theme={null}
  // Your Ditto server URL
  val endpoint = "REPLACE_ME_WITH_YOUR_URL"
  val id = "REPLACE_ME_WITH_YOUR_DATABASE_ID"
  val providerName = "YOUR_PROVIDER_NAME"

  val config = DittoConfig(
      databaseId = id, // This was "appID" in v4
      connect = DittoConfig.Connect.Server(url = endpoint)
  )

  val ditto = DittoFactory.create(config)

  // Set up authentication expiration handler (required for server connections).
  // `ditto.auth` is null when using Connect.SmallPeersOnly — guard with `?.let { }`.
  ditto.auth?.let { auth ->
      auth.expirationHandler = { ditto, timeUntilExpiration ->
          // The lambda is suspend — call login() directly. login() returns the
          // clientInfo JSON string (or null) and throws DittoException.AuthenticationException
          // on failure.
          try {
              val token = getMyToken()
              ditto.auth?.login(
                  token = token,
                  provider = providerName,
              )
              println("Authentication successful")
          } catch (error: DittoException.AuthenticationException) {
              println("Authentication failed: $error")
          }
      }
  }

  ditto.sync.start()
  ```

  ```java Java theme={null}
  // Your Ditto server URL
  String endpoint = "REPLACE_ME_WITH_YOUR_URL";
  String id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";
  String providerName = "YOUR_PROVIDER_NAME";

  DittoAndroidConfig config = new DittoAndroidConfig(
      context,
      id, // This was "appID" in v4
      new DittoConfigConnect.Server(new URL(endpoint))
  );

  Ditto ditto = Ditto.open(config);

  // Set up authentication expiration handler (required for server connections)
  ditto.getAuth().setExpirationHandler((dit, secondsRemaining) -> {
      // Authenticate when token is expiring
      // Get token from your authentication system
      String token = getMyToken();
      
      dit.getAuth().login(
          token,
          providerName,
          (clientInfo, error) -> {
              if (error != null) {
                  System.out.println("Authentication failed: " + error);
              } else {
                  System.out.println("Authentication successful");
              }
          }
      );
  });

  ditto.startSync();
  ```

  ```csharp C# theme={null}
  // Your Ditto server URL
  var endpoint = "REPLACE_ME_WITH_YOUR_URL";
  var id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";
  var providerName = "YOUR_PROVIDER_NAME";

  var config = new DittoConfig(
      databaseId: id, // This was "appID" in v4
      connect: new DittoConfigConnect.Server(new Uri(endpoint))
  );

  var ditto = await Ditto.OpenAsync(config);

  // Set up authentication expiration handler (required for server connections)
  ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) =>
  {
      // Authenticate when token is expiring
      try
      {
          // Get token from your authentication system
          var token = await GetMyTokenAsync();

          await ditto.Auth.LoginAsync(
              token,
              providerName
          );
          Console.WriteLine("Authentication successful");
      }
      catch (Exception error)
      {
          Console.WriteLine($"Authentication failed: {error}");
      }
  };

  ditto.Sync.Start();
  ```

  ```cpp C++ theme={null}
  // Your Ditto server URL
  std::string endpoint = "REPLACE_ME_WITH_YOUR_URL";
  std::string id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";
  std::string provider_name = "YOUR_PROVIDER_NAME";

  auto config = DittoConfig::default_config()
      .set_database_id(id) // This was "appID" in v4
      .set_connect(DittoConfig::Connect::server(endpoint));

  auto ditto = Ditto::open(config);

  // Set up authentication expiration handler (required for server connections)
  ditto->auth().set_expiration_handler([provider_name](auto& ditto, int64_t seconds_remaining) {
      // Authenticate when token is expiring
      // Get token from your authentication system
      std::string token = getMyToken();
      
      ditto.auth().login(
          token,
          provider_name,
          [](auto client_info, auto error) {
              if (error) {
                  std::cout << "Authentication failed: " << error->message() << std::endl;
              } else {
                  std::cout << "Authentication successful" << std::endl;
              }
          }
      );
  });

  ditto->start_sync();
  ```

  ```rust Rust theme={null}
  // Your Ditto server URL
  let endpoint = "REPLACE_ME_WITH_YOUR_URL";
  let id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";
  let provider_name = "YOUR_PROVIDER_NAME";

  // Create config with server connection
  let config = DittoConfig::default()
      .database_id(id) // This was "app_id" in v4
      .connect(Connect::Server {
          url: endpoint.to_string(),
      });

  // Initialize Ditto
  let ditto = Ditto::open(config)?;

  // Set up authentication expiration handler (required for server connections)
  ditto.auth().set_expiration_handler(move |ditto, seconds_remaining| {
      // Authenticate when token is expiring
      // Get token from your authentication system
      let token = get_my_token();
      
      ditto.auth().login(
          &token,
          provider_name,
          |client_info, error| {
              match error {
                  Some(e) => println!("Authentication failed: {}", e),
                  None => println!("Authentication successful"),
              }
          },
      );
  });

  ditto.start_sync()?;
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```
</CodeGroup>


## Related topics

- [Kotlin V4→V5 API Migration Guide](/sdk/latest/migration-guides/kotlin-v4.md)
- [Auth and Parameters](/cloud/http-api/auth-and-params.md)
- [Swift V4→V5 API Migration Guide](/sdk/latest/migration-guides/swift-v4.md)
