STEADYWATCH™ OIDC is a biometric-backed Identity Provider implementing RFC 8628 (Device Authorization Grant) — a direct replacement for Cisco DUO and similar push-based MFA systems. Authentication is initiated from any browser, delivered as a push notification to the user's iPhone, and completed via Face ID. No passwords, no TOTP codes, no hardware tokens.
Every authentication event produces a signed JWT with biometric attestation claims (sw_biometric_factor, sw_prime, sw_confidence) that your application can inspect.
This evaluation connects to a live, hosted instance. No IBM Quantum account, no Docker setup, no iOS app required to test the protocol. You need only:
curl (pre-installed on both)https://<current-tunnel-url>.trycloudflare.com
Get the current URL from the status page. Set it as a shell variable for convenience:
export SW_API="https://<current-tunnel-url>.trycloudflare.com"
SteadyWatch OIDC implements the OAuth 2.0 Device Authorization Grant (RFC 8628). The flow:
1. Your app → POST /oauth2/device/authorize (request auth)
← { device_code, user_code, verification_uri }
2. Your app → show user_code to user OR auto-approve via biometric push
3. iPhone ← push notification arrives → Face ID fires → approved
4. Your app → POST /oauth2/token (polling)
← { access_token, id_token } (JWT)
This is identical to how Google, GitHub, and Apple TV implement device login — except the approval step is biometric, not a browser click.
Verify the IdP is live and inspect its capabilities:
curl "$SW_API/.well-known/openid-configuration"
Expected response:
{
"issuer": "https://<tunnel-url>.trycloudflare.com",
"token_endpoint": ".../oauth2/token",
"jwks_uri": ".../oauth2/jwks",
"device_authorization_endpoint": ".../oauth2/device/authorize",
"grant_types_supported": ["urn:ietf:params:oauth:grant-type:device_code"],
"id_token_signing_alg_values_supported": ["RS256"]
}
curl -X POST "$SW_API/oauth2/device/authorize" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=steadywatch-demo&scope=openid profile"
Expected response:
{
"device_code": "sw_dev_abc123...",
"user_code": "XKCD-7291",
"verification_uri": "https://<tunnel>/oauth2/device/approve",
"expires_in": 300,
"interval": 5
}
Copy the device_code — you'll need it for token polling.
For evaluation without the iOS app, approve directly via the API:
curl -X POST "$SW_API/oauth2/device/approve" \
-H "Content-Type: application/json" \
-d '{"user_code": "XKCD-7291", "approved": true}'
In production, this step happens silently on the user's iPhone after Face ID passes.
curl -X POST "$SW_API/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=sw_dev_abc123...&client_id=steadywatch-demo"
Before approval: returns authorization_pending — poll every 5 seconds.
After approval:
{
"access_token": "eyJ...",
"id_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile"
}
The id_token is a signed JWT. Decode the payload (middle segment, base64):
# Quick decode (no verification) echo "eyJ..." | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
Expected claims:
{
"iss": "https://<tunnel-url>.trycloudflare.com",
"sub": "user_<device_id>",
"aud": "steadywatch-demo",
"exp": 1234567890,
"iat": 1234567890,
"sw_biometric_factor": "face_id",
"sw_prime": 5,
"sw_confidence": 0.97,
"sw_session_id": "sw_sess_..."
}
Custom claims:
| Claim | Meaning |
|---|---|
sw_biometric_factor | Biometric method used (face_id) |
sw_prime | Entropy prime used in key derivation (5, 13, or 17) |
sw_confidence | Face ID match confidence score (0–1) |
sw_session_id | Unique per-authentication session ID |
Fetch the public key and verify the JWT signature:
curl "$SW_API/oauth2/jwks"
Returns a standard JWKS document. Use any RFC 7517-compliant JWT library to verify:
import jwt, requests
jwks = requests.get(f"{SW_API}/oauth2/jwks").json()
# jwt.decode(id_token, jwks, algorithms=["RS256"], audience="steadywatch-demo")
| Feature | STEADYWATCH™ OIDC | Cisco DUO |
|---|---|---|
| Protocol | RFC 8628 (standard OIDC) | Proprietary |
| Approval mechanism | Face ID (biometric) | Push tap (no biometric) |
| Token format | Standard JWT | Proprietary session |
| Custom attestation claims | Yes | No |
| Self-hostable | Yes (Raspberry Pi to cloud) | No |
| Vendor lock-in | None — standard OIDC | High |
| Per-device key derivation | Yes (quantum-seeded) | No |
Browser / App
↓ POST /oauth2/device/authorize
Flask OIDC Provider (RFC 8628)
↓ APNs push notification
iPhone (STEADYWATCH™ HANDHELD)
↓ Face ID biometric verification
↓ Quantum key derivation (VAULT entropy + SE key)
Flask OIDC Provider
↓ Issues signed JWT with biometric claims
Browser / App
← access_token + id_token
Key properties:
Point your application's OIDC discovery URL to:
https://<tunnel-url>.trycloudflare.com/.well-known/openid-configuration
Compatible with Auth0, Okta (as external IdP), Keycloak, and any custom OAuth2 middleware.
Production deployments include client credentials, tenant isolation with per-tenant JWKS, rate limiting, audit logging, and custom claim mapping.
For integration support or production access: nathaniel.a.vazquez@gmail.com