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

# Bring Your Own Auth (BYOA)

> Authenticate your own users to EkaCare agents with a short-lived JWT signed by your shared secret (JWE optional)

## Overview

**Bring Your Own Auth (BYOA)** lets you authenticate your own users to EkaCare agents without an OIDC
flow. Your backend builds a short-lived token containing the user's identity, secures it with your
shared secret, and sends it to EkaCare — which looks up your secret by its **Key ID**, verifies the
token, and trusts the claims inside.

BYOA supports two token formats:

* **Signed JWT (JWS)** — **the default**. The claims are signed (HS256) with your shared secret. The
  payload is readable but tamper-proof. Lighter and simpler — use this unless you have a specific
  reason not to.
* **Encrypted JWE** — an optional, heavier alternative where the claims are *encrypted*. Use it only
  when the token travels somewhere it could be logged or inspected (e.g. a URL query parameter) **and**
  carries genuinely sensitive data. See [Encrypted Token (JWE)](#encrypted-token-jwe).

> [!NOTE]
>
> Both formats use the **same shared secret and Key ID** from the same BYOA credential. The `kid` header
> tells EkaCare which secret to use; the token format (signed vs encrypted) is detected automatically.

**Use BYOA when** your client has no OIDC/OAuth flow and you want to pass user data (mobile, name, etc.)
to EkaCare from your own backend.

***

## JWT or JWE?

> [!TIP]
>
> **Default to a signed JWT.** Reach for JWE only when **both** of these are true:
>
> 1. The token is sent somewhere observable — most commonly a **URL query parameter** (which can be
>    logged by proxies, servers, and browser history), rather than a header or request body, **and**
> 2. The payload contains **highly sensitive information** that must not be readable in transit.
>
> If you're passing the token in a header, request body, or the widget's `auth-token` attribute — which
> is the normal case — **use a JWT**. It's lighter, easier to debug, and just as secure against
> tampering.

| | Signed JWT (JWS) — default | Encrypted JWE |
| - | - | - |
| Claims visibility | Readable (base64), signed | Encrypted |
| Weight / complexity | Lighter, simpler | Heavier |
| Tamper protection | Yes | Yes |
| When to use | Almost always | Sensitive data on query params |

***

## Before You Begin

Create a BYOA credential in the [Eka Developer Console](https://console.eka.care). You will need:

* **Key ID** — the public identifier for your credential (e.g. `byoa_xxxxxxxxxxxxxxxx`); goes in the
  token's `kid` header.
* **Shared secret** — used to sign (JWT) or encrypt (JWE) the token. Shown only once at creation.
* **Issuer** — the issuer URL you registered on the credential; the token's `iss` claim must match it
  exactly.

> [!WARNING]
>
> The shared secret is shown only once. Store it securely on your backend and never expose it in
> client-side code. If it is lost or leaked, revoke the credential and create a new one.

***

## How It Works

**1. Create a credential**

In the [Eka Developer Console](https://console.eka.care) (BYOA → Create) you get a **Key ID** and a
**shared secret**. The secret is shown only once — copy it immediately.

**2. Build the claims**

On your backend, assemble the user's identity claims (see [Token Structure](#token-structure)).

**3. Sign as a JWT**

Sign the claims with your shared secret using **HS256**, with the protected header `kid` set to your
Key ID. (Or, for the sensitive-query-param case, [encrypt as a JWE](#encrypted-token-jwe).)

**4. Send it**

Hand the token to your embedded MedAssist widget — via the `auth-token` attribute or
`EkaMedAssist.init({ authToken })`. See [Send the Token](#send-the-token).

**5. EkaCare verifies**

EkaCare looks up your secret and registered issuer by the `kid`, verifies the signature, and trusts
the verified claims.

***

## Token Structure

The `x-auth-token` is a compact **JWT** (JWS) with a signed header and a payload of claims.

### Header

```json theme={null}
{ "kid": "byoa_xxxxxxxxxxxxxxxx", "alg": "HS256", "typ": "JWT" }
```

* `kid` — your credential's Key ID.
* `alg` — `HS256` (HMAC-SHA256; the shared secret is the signing key).
* `typ` — `JWT`.

### Payload claims

**iss** `string` *required*

Your issuer — must exactly match the issuer registered on your credential.

**aud** `string` *required*

Intended audience. Always `https://eka.care`.

**sub** `string`

Subject — your stable user identifier (partner user id). Leave it empty (`""`) if you identify users
only by mobile number (e.g. WhatsApp), where the `mobile` claim is the identifier instead.

**mobile** `string`

The user's mobile number, with country code.

**iat** `number` *required*

Issued-at time, in epoch seconds (UTC).

**exp** `number` *required*

Expiry time, in epoch seconds. Keep it short — `iat + 300` (about 5 minutes).

**jti** `string`

Recommended. A unique ID per request so EkaCare can reject replays of the same token.

Example payload:

```json theme={null}
{
  "iss": "https://partner.example.com",
  "aud": "https://eka.care",
  "sub": "partner_user_12345",
  "mobile": "+919876543210",
  "iat": 1749600000,
  "exp": 1749600300,
  "jti": "b3f1c2a4-9e5d-4c8b-a1f2-0d7e6c5b4a39"
}
```

> [!NOTE]
>
> The shared secret is a base64url-encoded 32-byte key. Decode it to 32 raw bytes before using it as the
> HS256 HMAC signing key.

***

## Generate the Token

Build the claims and sign them as a JWT with your shared secret (`HS256`, header `kid`). The shared
secret is base64url-decoded to a 32-byte key.

```python Python theme={null}
import base64
import time
import uuid
import jwt  # PyJWT


def mint_partner_token() -> str:
    # Provided by Eka
    kid    = "byoa_xxxxxxxxxxxxxxxx"
    secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk"

    # base64url-decode the secret to 32 raw bytes (the HMAC key)
    raw = base64.urlsafe_b64decode(secret + "==")

    # Your values
    now = int(time.time())
    claims = {
        "iss":    "https://partner.example.com",
        "aud":    "https://eka.care",
        "sub":    "partner_user_12345",  # leave "" if you only have mobile
        "mobile": "+919876543210",
        "iat":    now,
        "exp":    now + 300,
        "jti":    str(uuid.uuid4()),
    }

    return jwt.encode(claims, raw, algorithm="HS256", headers={"kid": kid})


token = mint_partner_token()
print(token)
```

```javascript Node.js theme={null}
import { SignJWT } from 'jose';
import { randomUUID } from 'crypto';

async function mintPartnerToken() {
    const kid = "byoa_xxxxxxxxxxxxxxxx";
    const secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk";

    // base64url-decode the secret to 32 raw bytes (the HMAC key)
    const key = Buffer.from(secret, "base64url");

    const now = Math.floor(Date.now() / 1000);

    return await new SignJWT({
        sub: "partner_user_12345",  // leave "" if you only have mobile
        mobile: "+919876543210",
    })
        .setProtectedHeader({ alg: "HS256", kid })
        .setIssuer("https://partner.example.com")
        .setAudience("https://eka.care")
        .setIssuedAt(now)
        .setExpirationTime(now + 300)
        .setJti(randomUUID())
        .sign(key);
}

mintPartnerToken()
    .then(token => {
        console.log("\nGenerated JWT:\n");
        console.log(token);
    })
    .catch(console.error);
```

```go Go theme={null}
package main

import (
	"encoding/base64"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

func mintPartnerToken() (string, error) {
	kid := "byoa_xxxxxxxxxxxxxxxx"
	secret := "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk"

	// base64url-decode the secret to 32 raw bytes (the HMAC key)
	key, err := base64.RawURLEncoding.DecodeString(secret)
	if err != nil {
		return "", err
	}

	now := time.Now().Unix()
	claims := jwt.MapClaims{
		"iss":    "https://partner.example.com",
		"aud":    "https://eka.care",
		"sub":    "partner_user_12345", // leave "" if you only have mobile
		"mobile": "+919876543210",
		"iat":    now,
		"exp":    now + 300,
		"jti":    uuid.NewString(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token.Header["kid"] = kid
	return token.SignedString(key)
}

func main() {
	token, err := mintPartnerToken()
	if err != nil {
		panic(err)
	}

	fmt.Println(token)
}
```

```java Java theme={null}
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.keys.HmacKey;

import java.util.Base64;

public class GenerateJWT {

    public static void main(String[] args) throws Exception {

        String kid = "byoa_xxxxxxxxxxxxxxxx";
        String secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk";

        // base64url-decode the secret to 32 raw bytes (the HMAC key)
        byte[] keyBytes = Base64.getUrlDecoder().decode(secret);

        JwtClaims claims = new JwtClaims();
        claims.setIssuer("https://partner.example.com");
        claims.setAudience("https://eka.care");
        claims.setSubject("partner_user_12345"); // leave "" if you only have mobile
        claims.setClaim("mobile", "+919876543210");
        claims.setIssuedAtToNow();
        claims.setExpirationTimeMinutesInTheFuture(5);
        claims.setGeneratedJwtId();

        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setKey(new HmacKey(keyBytes));
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
        jws.setKeyIdHeaderValue(kid);

        System.out.println(jws.getCompactSerialization());
    }
}
```

***

## Send the Token

The **MedAssist widget** is EkaCare's embeddable chat widget. Copy its embed snippet from your agent's
**Widget** tab in the [Developer Console](https://console.eka.care) (or see the
[widget quickstart](https://developer.eka.care/docs/agents/synapse/embed/quickstart.md)), then add the token to it.

![The Widget tab in the EkaCare Developer Console, showing the embed code to copy](https://mintcdn.com/ekacare/WPS74CI10j1wFPau/images/widget-tab.png?fit=max\&auto=format\&n=WPS74CI10j1wFPau\&q=85\&s=c12a6e11b590bcd4d48c6ee20c8c165f)

There are two ways to pass the token:

```html Custom element theme={null}
<eka-medassist-widget
  agent-id="YOUR_AGENT_ID"
  auth-token="COMPACT_JWT">
</eka-medassist-widget>

<script
  src="https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js"
  async
></script>
```

```html JavaScript theme={null}
<eka-medassist-widget agent-id="YOUR_AGENT_ID"></eka-medassist-widget>
<script
  src="https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js"
  async
></script>

<script>
  window.EkaMedAssist.init({
    agentId: "YOUR_AGENT_ID",
    authToken: "COMPACT_JWT",
  });
</script>
```

* **Custom element** — set the `auth-token` attribute on the `<eka-medassist-widget>` element. Best
  when you can render the token into the page server-side. See the
  [widget quickstart](https://developer.eka.care/docs/agents/synapse/embed/quickstart.md).
* **JavaScript** — pass `authToken` to `EkaMedAssist.init()`. Best when the token is dynamic (e.g.
  minted per logged-in user at runtime). See the
  [JavaScript API](https://developer.eka.care/docs/agents/synapse/embed/javascript-api.md).

***

## How EkaCare Verifies

When EkaCare receives the token, it:

1. Reads the `kid` from the token header and looks up the matching **shared secret** and registered
   **issuer**.
2. Verifies the JWT signature with your secret (or decrypts it, if it's a JWE).
3. Verifies the claims — `iss` matches the registered issuer, `aud` is `https://eka.care`, and `iat`
   and `exp` are within the allowed window. If you include a `jti`, it must not have been seen before
   (replay protection).

If any check fails, the request is rejected.

***

## Encrypted Token (JWE)

> [!NOTE]
>
> JWE is an **optional alternative to the default signed JWT**. It's heavier than a JWT. Only use it when
> the token is sent somewhere observable — most commonly a **URL query parameter** (which can be logged
> by proxies, servers, and browser history) — **and** its payload holds highly sensitive information that
> must not be readable in transit. Otherwise, use a [signed JWT](#generate-the-token).

A JWE carries the **same claims** as the JWT above ([Token Structure](#token-structure)) and uses the
**same Key ID and shared secret** — the payload is *encrypted* instead of signed.

### Header

```json theme={null}
{ "kid": "byoa_xxxxxxxxxxxxxxxx", "alg": "dir", "enc": "A256GCM" }
```

* `kid` — your credential's Key ID.
* `alg` — `dir` (the shared secret is used directly as the encryption key).
* `enc` — `A256GCM` (content encryption).

> [!NOTE]
>
> The shared secret is a base64url-encoded 32-byte key. Decode it to 32 raw bytes before using it as the
> `A256GCM` content-encryption key.

### Generate the JWE

Build the claims, encrypt them as a JWE with your shared secret (`alg: dir`, `enc: A256GCM`, header
`kid`), and serialize to compact form. The shared secret is base64url-decoded to a 32-byte key.

```python Python theme={null}
import base64
import time
import orjson
from jwcrypto import jwe, jwk

def mint_partner_token() -> str:
    # Provided by Eka
    kid    = "byoa_xxxxxxxxxxxxxxxx"
    secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk"

    # Your values
    now = int(time.time())
    claims = {
        "iss":    "https://partner.example.com",
        "aud":    "https://eka.care",
        "sub":    "partner_user_12345",
        "mobile": "+919876543210",
        "iat":    now,
        "exp":    now + 300,
    }

    # Build key
    padded  = secret + "=" * (-len(secret) % 4)
    raw     = base64.urlsafe_b64decode(padded)
    k_b64   = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
    jwk_key = jwk.JWK(kty="oct", k=k_b64)

    # Encrypt
    token_obj = jwe.JWE(
        plaintext=orjson.dumps(claims),
        protected=orjson.dumps({"alg": "dir", "enc": "A256GCM", "kid": kid}).decode(),
    )
    token_obj.add_recipient(jwk_key)
    return token_obj.serialize(compact=True)


token = mint_partner_token()
print(token)
```

```javascript Node.js theme={null}
import { CompactEncrypt } from 'jose';

async function mintPartnerToken() {
    const kid = "byoa_xxxxxxxxxxxxxxxx";
    const secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk";

    const now = Math.floor(Date.now() / 1000);

    const claims = {
        iss: "https://partner.example.com",
        aud: "https://eka.care",
        sub: "partner_user_12345",
        mobile: "+919876543210",
        iat: now,
        exp: now + 300,
    };

    const key = Buffer.from(secret, "base64url");

    const token = await new CompactEncrypt(
        Buffer.from(JSON.stringify(claims))
    )
        .setProtectedHeader({
            alg: "dir",
            enc: "A256GCM",
            kid,
        })
        .encrypt(key);

    return token;
}

mintPartnerToken()
    .then(token => {
        console.log("\nGenerated JWE:\n");
        console.log(token);
    })
    .catch(console.error);
```

```go Go theme={null}
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"time"

	jose "github.com/go-jose/go-jose/v4"
)

func mintPartnerToken() (string, error) {
	kid := "byoa_xxxxxxxxxxxxxxxx"
	secret := "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk"

	now := time.Now().Unix()

	claims := map[string]interface{}{
		"iss":    "https://partner.example.com",
		"aud":    "https://eka.care",
		"sub":    "partner_user_12345",
		"mobile": "+919876543210",
		"iat":    now,
		"exp":    now + 300,
	}

	payload, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}

	key, err := base64.RawURLEncoding.DecodeString(secret)
	if err != nil {
		return "", err
	}

	encrypter, err := jose.NewEncrypter(
		jose.A256GCM,
		jose.Recipient{
			Algorithm: jose.DIRECT,
			Key:       key,
		},
		(&jose.EncrypterOptions{}).WithHeader("kid", kid),
	)
	if err != nil {
		return "", err
	}

	obj, err := encrypter.Encrypt(payload)
	if err != nil {
		return "", err
	}

	return obj.CompactSerialize()
}

func main() {
	token, err := mintPartnerToken()
	if err != nil {
		panic(err)
	}

	fmt.Println(token)
}
```

```java Java theme={null}
import org.jose4j.jwe.JsonWebEncryption;
import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers;
import org.jose4j.keys.AesKey;

import java.util.Base64;

public class GenerateJWE {

    public static void main(String[] args) throws Exception {

        String kid = "byoa_xxxxxxxxxxxxxxxx";
        String secret = "dGVzdHNlY3JldGtleWZvcmp3ZXRlc3QxMjM0NTY3ODk";

        long now = System.currentTimeMillis() / 1000;

        String payload = String.format("""
        {
          "iss":"https://partner.example.com",
          "aud":"https://eka.care",
          "sub":"partner_user_12345",
          "mobile":"+919876543210",
          "iat":%d,
          "exp":%d
        }
        """, now, now + 300);

        byte[] keyBytes = Base64.getUrlDecoder().decode(secret);
        AesKey key = new AesKey(keyBytes);

        JsonWebEncryption jwe = new JsonWebEncryption();

        jwe.setPayload(payload);
        jwe.setAlgorithmHeaderValue(
                KeyManagementAlgorithmIdentifiers.DIRECT);

        jwe.setEncryptionMethodHeaderParameter(
                ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);

        jwe.setKeyIdHeaderValue(kid);
        jwe.setKey(key);

        String token = jwe.getCompactSerialization();

        System.out.println(token);
    }
}
```

EkaCare detects the JWE format automatically, looks up your secret by `kid`, decrypts it, and verifies
the same claims as a JWT.

***

## Best Practices

> [!WARNING]
>
> * Generate tokens **server-side only** — never ship the shared secret to a browser or mobile app.
> * Prefer a **signed JWT**; reach for JWE only for sensitive data on query parameters.
> * Keep `exp` short (\~5 minutes) and use a fresh `jti` for every request.
> * Rotate by revoking the credential and creating a new one, then update your agents to the new Key ID.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.