Skip to main content
Every time a request shows up at your agent’s door, the agent has to answer one question before it does anything else:
Should I actually answer this?
Sounds simple. It’s not. Hidden inside that one question are really two questions, and most tutorials smush them together and make a mess. Picture a stranger at your door holding an envelope. Before you let them in, you want to know:
  1. Who are they? The name on the envelope could be real. It could also be something they scribbled on the bus.
  2. Are they allowed in? Even if the name is real — do they have permission to enter this room, right now?
In Bindu we keep those two questions separate, because they need totally different tools to answer.
QuestionWhat answers itWhere it lives
Are you allowed to make this request?Authentication (this page) — a token from a trusted serviceOAuth 2.0 / Ory Hydra
Is this request really from who it claims to be?DID signing — a signature only the real person can makeDID page
You almost always need both. This page handles the first one. After this, go read the DID page. Together they tell the whole story.

Bearer tokens — the movie ticket trick

Ever been to a movie? The person at the door doesn’t ask your name, your address, your favourite childhood pet. They just want to see your ticket. You hand it over, they tear off the stub, you walk in. The ticket is the proof. Whoever’s holding it gets in. That’s literally why it’s called a bearer token — “bearer” meaning “whoever’s bearing it right now.”
Your cat could walk in with your bearer token and the movie theatre wouldn’t stop them. Your cat would not enjoy the movie.
A bearer token in HTTP works the same way. It’s a random-looking string. Your client staples it to every request. The server sees it, decides “yep, that’s valid,” and lets the request through. No further questions. A real Bindu bearer token looks like this:
ory_at_hV2cm_iq55iipi8M53mwvQbpNwQNfTTxvJnDlOWFRYw.I8V_GL5s2afZTh_ZMpshauGpnItx7iItBc6pgVRAOVg
You send it in an HTTP header:
Authorization: Bearer ory_at_hV2cm_iq55iipi...
That’s the whole “authentication” part of Bindu. Client attaches a token. Server checks it. Done. Two things follow from “whoever holds it, gets in”:
Treat tokens like passwords. A leaked token is an open door. Don’t paste them in Slack. Don’t commit them to git. Don’t log them. Don’t text them to your friend even as a joke.
  • They expire. Bindu tokens last about an hour. If one leaks, the damage window is small.

Where do tokens come from? Meet Hydra

OK — but who gives out the tokens? Not the agent, surely. That’d be like asking the movie theatre door-checker to also run the ticket booth. Too much trust in one place. Bindu uses a separate service whose entire job is issuing and validating tokens: Ory Hydra. It’s an open-source OAuth 2.0 server. Battle-tested. Used by serious companies. We don’t build our own, because “token issuance” is one of those things that looks easy, is actually full of foot-guns, and gets one tiny thing wrong and suddenly the internet has access to everything. Hydra shows up as two URLs:
URLPurposeWho talks to it
https://hydra.getbindu.comPublic — hands out tokens. Endpoints like /oauth2/token.Clients (your code, Postman, the gateway)
https://hydra-admin.getbindu.comAdmin — registers clients, checks if a token is valid. Endpoints like /admin/*.Agents, registration scripts
Both URLs point at the same Hydra process with the same database behind it. Register a client on the admin side — the public side sees it immediately. No sync, no delay.
The admin URL is powerful. Anyone who can reach /admin/clients can register new clients or read secrets. In production, the admin URL lives on a private network. Never expose it to the open internet.

The whole flow, from zero to answered

Here’s what happens end to end when a client wants to talk to an agent. Three stages, different frequencies:
┌─────────┐           ┌──────────────┐        ┌──────────────┐        ┌───────┐
│ Client  │           │ Hydra admin  │        │ Hydra public │        │ Agent │
└────┬────┘           └──────┬───────┘        └──────┬───────┘        └───┬───┘
     │                       │                       │                    │
     │ 1. Register (once)    │                       │                    │
     ├──────────────────────▶│                       │                    │
     │  got a client_secret  │                       │                    │
     │◀──────────────────────┤                       │                    │
     │                                                                    │
     │ 2. Swap secret for token (once per hour)                          │
     ├──────────────────────────────────────────────▶│                    │
     │  access_token, valid ~1h                                          │
     │◀──────────────────────────────────────────────┤                    │
     │                                                                    │
     │ 3. Call agent with Authorization: Bearer <token> (every request)  │
     ├───────────────────────────────────────────────────────────────────▶│
     │                       │                                            │
     │                       │ 4. Is this token still valid?             │
     │                       │◀───────────────────────────────────────────┤
     │                       │   active=true, expires in X                │
     │                       ├───────────────────────────────────────────▶│
     │                                                                    │
     │ 5. The response                                                    │
     │◀───────────────────────────────────────────────────────────────────┤
In plain English:
  • Step 1 — once per client. You introduce yourself to Hydra. It writes down who you are and gives you a secret. You probably do this once, when a new client is provisioned.
  • Step 2 — once per hour. You trade the long-lived secret for a short-lived token. The secret is like a key to your apartment. The token is like a concert wristband — lasts one night.
  • Steps 3–5 — every single request. You attach the token. The agent asks Hydra “hey, is this thing still good?” — that question is called token introspection. If yes, request goes through.
Why does the agent ask Hydra every time? Because the token is opaque — a handle, not a document. The agent can’t read it directly. Only Hydra knows what it means.

What’s actually inside a token?

Nothing readable. The token string itself is random-looking on purpose. All the meaning lives in Hydra’s database. When the agent introspects a token, Hydra sends back something like this:
{
  "active":     true,
  "client_id":  "did:bindu:dutta_raahul_at_gmail_com:postman:ee67868d-d4b6-...",
  "sub":        "did:bindu:dutta_raahul_at_gmail_com:postman:ee67868d-d4b6-...",
  "scope":      "openid offline agent:read agent:write",
  "exp":        1776622403,
  "iat":        1776618803,
  "token_type": "Bearer"
}
Quick read, top to bottom:
  • active: true — Hydra still thinks this token is good. Expired or revoked? This flips to false and the request is rejected.
  • client_id / sub — who the token belongs to. In Bindu this is almost always a DID. (The DID page explains why.)
  • scope — what rooms of the house this ticket opens. agent:read = read-only. agent:write = can change stuff.
  • exp — when the token turns into a pumpkin. Unix timestamp.
  • iat — when it was issued.
The agent reads this, decides if it likes what it sees, and passes the client_id along to the request handler so your code knows who just walked in.

Turning authentication on

Auth is off by default in development, because nobody wants to register a Hydra client just to say hello to localhost. To turn it on, set a handful of environment variables:
# Master switch
AUTH__ENABLED=true

# Only Hydra today
AUTH__PROVIDER=hydra

# Where your Hydra lives
HYDRA__ADMIN_URL=https://hydra-admin.getbindu.com
HYDRA__PUBLIC_URL=https://hydra.getbindu.com
The double underscore (__) is how Bindu flattens nested config into env vars. AUTH__ENABLED maps to settings.auth.enabled. You don’t need to think about the mapping — just set the variables. Once your agent starts with these, the middleware:
  1. Wires itself up to talk to Hydra admin for introspection.
  2. Rejects any incoming request without a valid Authorization: Bearer ... header.
  3. Attaches the introspection result to the request so your handler knows who’s calling.

Getting your first token

1

Register your client with Hydra

Think of this like opening an account at a bank. You hand Hydra your ID, Hydra files the paperwork, gives you a password.
curl -X POST 'https://hydra-admin.getbindu.com/admin/clients' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id":     "did:bindu:your_email_at_example_com:your_agent:<uuid>",
    "client_secret": "<pick a strong random value>",
    "grant_types":   ["client_credentials"],
    "response_types": ["token"],
    "scope":         "openid offline agent:read agent:write",
    "token_endpoint_auth_method": "client_secret_post"
  }'
Quick notes on each field:
  • client_id — your agent’s name in Hydra. In Bindu this is always a DID (see the DID page for why).
  • client_secret — the password you’ll use to get tokens. Generate something strong:
    openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'
    
    Store it like a database password. You need it again in the next step.
  • grant_types: client_credentials — “I’m a server, not a human in a browser.” No login forms, no redirects. Swap secret → token.
  • scope — the permissions you want your tokens to carry. Ask for the minimum.
  • token_endpoint_auth_method: client_secret_post — you’ll send the secret in the request body, not a header. Both work; we use post for compatibility.
2

Swap the secret for a token

Once an hour (or the first time, or any time your token is about to expire):
curl -X POST 'https://hydra.getbindu.com/oauth2/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=did:bindu:your_email_at_example_com:your_agent:<uuid>' \
  -d 'client_secret=<the secret from step 1>' \
  -d 'scope=openid offline agent:read agent:write'
Hydra sends back:
{
  "access_token": "ory_at_...long opaque string...",
  "expires_in":   3599,
  "scope":        "openid offline agent:read agent:write",
  "token_type":   "bearer"
}
Copy that access_token. Keep it in memory. Refresh it before it expires.
3

Use the token

curl --location 'http://localhost:3773/' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer ory_at_...' \
  --data '{
      "jsonrpc": "2.0",
      "method":  "message/send",
      "params":  { "message": { "role": "user", "content": "Hello!" } },
      "id":      1
  }'
If the agent responds, you’re authenticated. If it rejects you with a 401 or 403, the next section is for you.

What can go wrong (and how to read the error)

This is where most beginners lose an afternoon. Here’s the cheat sheet — find your error, fix that thing, not something else.
You seeMost likely becauseFix
401 Unauthorized, no Authorization headerYou forgot the tokenAdd Authorization: Bearer <token>
401 Unauthorized, introspection says active: falseToken expiredGet a fresh one (step 2 above)
401 Unauthorized, token doesn’t existToken is from a different Hydra than the agent usesCheck HYDRA__ADMIN_URL — agent and client must agree
invalid_client at the token endpointWrong client_secret, wrong client_id, or the client doesn’t exist yetRegister first, double-check the secret
invalid_scope at the token endpointAsking for a scope you didn’t register withRegister with more scopes, or ask for less
Token works for one method, fails for anotherThat method needs a specific scope you didn’t requestRequest the right scope, get a new token
There’s one sneaky one worth calling out separately, because it sends people on long debugging wild-goose chases:
Symptom: introspecting against one Hydra URL says the token is valid. But the agent says it’s invalid.Cause: the agent is talking to a different Hydra than the one that issued the token. Classic case: dev laptop is pointing at a local Hydra, but the token came from production.Fix: make sure HYDRA__ADMIN_URL on the agent matches the Hydra that issued the token. The agent prints this on startup — check the log.

Finding your credentials again

Two things you’ll need to look up at some point:
  • Your agent’s DID — it’s in the agent card:
    curl http://localhost:3773/.well-known/agent.json
    
    Look for the agent.did field. That’s also your client_id for Hydra.
  • Your client secret from bindufy — saved at .bindu/oauth_credentials.json. Treat that file like .ssh/id_rsa. User-readable only, never committed.
If you’ve lost the secret entirely, rotate it by PUTing to /admin/clients/<client_id> with a new secret.

The UI shortcut

The Bindu frontend has a Settings → Authentication page that does step 2 for you. Paste your client secret, click a button, get a token. Handy when you’re poking around in Postman. It’s a convenience. Not a replacement for understanding what’s happening underneath.

What’s next

Authentication answers “are you allowed in?” — but in a world where agents talk to agents that talk to other agents, you need something stronger: “are you really who you claim to be?” That’s where DIDs come in. Keep reading.

DIDs (next up)

Cryptographic identity — how agents prove they’re really them

API Reference

The full HTTP surface of a Bindu agent

Appendix: commands you’ll reach for

Register a client:
curl -X POST 'https://hydra-admin.getbindu.com/admin/clients' \
  -H 'Content-Type: application/json' \
  -d '{ ...see step 1... }'
Look a client up (does it exist? what metadata does it have?):
curl 'https://hydra-admin.getbindu.com/admin/clients/<client_id>'
Update a client (rotate secret, change metadata):
curl -X PUT 'https://hydra-admin.getbindu.com/admin/clients/<client_id>' \
  -H 'Content-Type: application/json' \
  -d '{ ...full client record with changes... }'
Delete a client (careful — this breaks existing tokens):
curl -X DELETE 'https://hydra-admin.getbindu.com/admin/clients/<client_id>'
Introspect a token (debug “is this thing valid?”):
curl -X POST 'https://hydra-admin.getbindu.com/admin/oauth2/introspect' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'token=<your access token>'
Generate a strong secret:
openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'