Authentication
dxflow offers two ways to sign in, and both end in a JWT session with a configurable lifetime.
- Private key — challenge-response with an RSA key pair, so you prove you own the key without ever sending it. The browser remembers yours, making repeat sign-ins a single click. Multiple keys are supported, each with its own permissions.
- Pairing code — a short code printed by the engine host and approved there. Suited to a server you reach over SSH, where copying the key to your laptop would be the only alternative.
Signing in with a private key
Locate your private key
The first time an engine starts it creates a key pair for itself, writes the private half to ~/.dxflow/private-key.pem (mode 600), and authorizes the public half with MASTER. That file is the key to sign in with — copy it to the machine running the browser.
To issue an additional key, run dxflow key generate [NAME]. It writes <name>.pem (private) and <name>.pub (public) to your current directory (default name rsa); register the .pub with dxflow key register <name>.pub.
dxflow key list shows the public keys authorized on the engine (stored in ~/.dxflow/authorized_keys), not your local private key files.
Open the login page
- Local installation:
http://localhost(or your configured port) - Remote server:
http://<your-server-ip>:<port>
Sign in
Choose Private key as the method, pick a session duration, and select your .pem or .key file. The key stays on your device.
Signing in with a pairing code
Use this when the engine runs somewhere you have shell access, such as an EC2 instance or any cloud VM. The private key stays on the server.
Ask the engine for a code
On the machine running the engine:
dxflow engine pair
It prints a code and waits:
● Pairing
Permissions SHELL, ARTIFACT, RUNTIME, WORKFLOW, AGENT
Code K7QM3XPD
Waiting 30s for a console to use the code
Enter the code in the console
Open the console, choose Pairing code as the method, and type the code. Opening http://<your-server-ip>:<port>/#pair=K7QM3XPD fills it in for you.
Approve the request
The terminal shows who is asking, and answering y signs the console in:
● Pairing request
Address 203.0.113.9
Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Approve? [y/N] y
Pairing approved: the console is signed in
Scope the session with flags on the command:
dxflow engine pair --permissions SHELL,RUNTIME # limit what the session can do
dxflow engine pair --lifetime 24h # 1 minute to 24 hours, default 1 hour
A code lasts 30 seconds, and you get a further 30 seconds to answer once a console claims it. It works once — approving, rejecting, and letting it expire all consume it — so every session starts with a fresh dxflow engine pair. The browser keeps nothing afterwards, which makes pairing a good fit for a shared or borrowed machine.
Key management
# List authorized keys (by IDENTITY)
dxflow key list
# Generate a new key pair (optional NAME, default "rsa")
dxflow key generate [NAME]
# Register an additional public key
dxflow key register <public-key-file>
# Remove a key
dxflow key unregister <key-identity>
Register a key with a narrower scope by passing --permissions; a key registered without them receives SHELL,ARTIFACT,RUNTIME,WORKFLOW,AGENT. See the permission model.
Session management
Sessions use JWT tokens with a lifetime of 1 minute to 24 hours (default 1 hour). They:
- Persist across browser tabs and survive page refresh and navigation
- Renew automatically during active use and stay in sync across tabs
- End on logout, and expired tokens are cleaned up automatically
Calling the API directly
To authenticate from your own code, use these three endpoints. You sign the challenge with your private key, so the key is never sent.
| Step | Endpoint | What it returns |
|---|---|---|
| Request a challenge | GET /api/auth/challenge/ | identity, nonce, lifetime |
| Sign and verify | POST /api/auth/verify/ | A JWT token, permissions, and expires_at |
| Check a session | GET /api/auth/inspect/ | Confirms whether the JWT (sent in the request headers) is still valid |
Pairing uses one endpoint from a client's side: POST /api/auth/pair/request/ with { "code": "K7QM3XPD" }. It streams a confirmation that the code was accepted, holds the connection while the operator decides, and answers with the same token, permissions, and expires_at on approval. A rejected, unknown, expired, or already-claimed code answers 401.
Challenge response:
{
"identity": "your-key-identity",
"nonce": "random-challenge-string",
"lifetime": "1m0s"
}
Verify response. Send an optional lifetime (e.g. "1h") to set the token duration. Range is 1 minute to 24 hours; out-of-range or omitted values use the 1 hour default.
{
"token": "jwt-authentication-token",
"permissions": ["SHELL", "ARTIFACT", "WORKFLOW"],
"expires_at": 1640995200
}
Troubleshooting
| Symptom | Fix |
|---|---|
| Cannot locate a private key | The engine's own key is at ~/.dxflow/private-key.pem on the engine host. dxflow key list shows what is authorized, not local files. |
| The private key won't parse | dxflow reads PKCS#1/PKCS#8 PEM. An ssh-keygen key needs -m PEM — convert an existing one with ssh-keygen -p -m PEM -f <key>. |
| Signature verification fails | The private key doesn't match any registered public key. Register it, or generate a fresh pair. |
401 Unauthorized from the CLI | dxflow config unset connection-token && dxflow engine ping forces a fresh token. |
| Authentication fails on a correct key | Check the system clock — token validity is time-based. |
invalid pairing code | The 30-second window passed, the code was already claimed, or a character was mistyped. Run dxflow engine pair again. |
pairing expired on approval | The decision window is 30 seconds. Have the code typed in the browser before you answer. |
| Permission denied on an operation | The key lacks that permission, or the license does not grant the module. Check with dxflow key list and dxflow license info. |
dxflow key unregister <identity>, register a replacement, and update anything that used the old one.