Authentication
You sign a JWT saying who the visitor is. Vatio verifies it and passes it to your tools. That is the whole of authentication — there is one mechanism, on every channel.
Vatio holds only your public key, so it can check a token but never mint one. Your backend stays the only thing that can say who someone is.
Set it up
Generate the keypair:
vatio auth --new-keyThat writes two files, and they go to different places.
identity.pub stays in the workspace. It is a public key, so it is committed like any other file and vatio.yml names it:
# vatio.yml
auth:
public_key: identity.pubidentity.pem goes into your own backend, as a secret — an environment variable, or whatever credential store you already use. Your backend is what signs, so it is the only thing that ever needs it. Load it, then delete the file from the workspace directory; it has no job there.
# environment variable
VATIO_IDENTITY_PRIVATE_KEY="$(cat identity.pem)"
# or Rails credentials
bin/rails credentials:edit # vatio: { identity_private_key: "..." }Never vatio secrets set the private key. That store is read by Vatio, to let your tools call your API. A private key in there would let Vatio mint tokens for your users instead of only checking them, which is the one thing this design exists to prevent. Vatio holds the public half and nothing else.
Mark every tool that needs a signed-in user:
# tools/my_bookings.yml
access: privateTools without access: private are public and run for anyone.
The token
Sign it with identity.pem using RS256:
| Claim | Required | Value |
|---|---|---|
sub | yes | Your user id. Becomes $auth.subject. |
aud | yes | Your workspace slug, exactly. |
exp | yes | Unix seconds. Keep it short — Vatio re-checks it on every tool call. |
name | no | Fills the contact's name. |
email | no | Fills the contact's email. |
phone_number | no | Fills the contact's phone. |
Any other claim you add arrives as $auth.claims.<name>.
# Ruby
JWT.encode(
{ sub: user.id.to_s, aud: "acme", exp: 1.hour.from_now.to_i,
name: user.name, email: user.email },
OpenSSL::PKey::RSA.new(ENV["VATIO_IDENTITY_PRIVATE_KEY"]), "RS256"
)// Node (jsonwebtoken)
jwt.sign(
{ sub: String(user.id), name: user.name, email: user.email },
process.env.VATIO_IDENTITY_PRIVATE_KEY,
{ algorithm: "RS256", audience: "acme", expiresIn: "1h" }
);# Python (PyJWT)
jwt.encode(
{"sub": str(user.id), "aud": "acme",
"exp": int(time.time()) + 3600, "name": user.name},
os.environ["VATIO_IDENTITY_PRIVATE_KEY"], algorithm="RS256",
)aud is required because a signature proves who signed, not what for. Without it, any other JWT you sign with the same key — a password reset, a download link — would be accepted here as an identity.
Next: how the token reaches Vatio on each channel, and what a private tool sees once it does.
