Refine auth flow: server-provided reauth challenge for expired tokens.
Two rejection paths: expired-but-valid tokens get a ReauthChallenge with a server-generated nonce (fast path, saves a round trip). Invalid/corrupted tokens get plain Unauthenticated (client falls back to full self-generated auth flow). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -198,29 +198,55 @@ service GardenSync {
|
|||||||
2. Client attaches token as `x-sgard-auth-token` gRPC metadata
|
2. Client attaches token as `x-sgard-auth-token` gRPC metadata
|
||||||
3. Server verifies JWT signature and expiry
|
3. Server verifies JWT signature and expiry
|
||||||
4. If valid → request proceeds
|
4. If valid → request proceeds
|
||||||
5. If expired or invalid → server returns `Unauthenticated`
|
|
||||||
|
|
||||||
**Auto-renewal flow (transparent to user):**
|
**Token rejection — two cases:**
|
||||||
1. Client sends request with cached token
|
|
||||||
2. Server rejects with `Unauthenticated`
|
The server distinguishes between an expired-but-previously-valid token
|
||||||
3. Client interceptor catches the error
|
and a completely invalid one:
|
||||||
4. Client calls `Authenticate` RPC with SSH signature
|
|
||||||
5. Server issues new JWT
|
- **Expired token** (valid signature, known fingerprint still in
|
||||||
|
authorized_keys, but past expiry): server returns `Unauthenticated`
|
||||||
|
with a `ReauthChallenge` — a server-generated nonce embedded in the
|
||||||
|
error details. This is the fast path.
|
||||||
|
|
||||||
|
- **Invalid token** (bad signature, unknown fingerprint, corrupted):
|
||||||
|
server returns a plain `Unauthenticated` with no challenge. The client
|
||||||
|
falls back to the full Authenticate flow.
|
||||||
|
|
||||||
|
**Fast re-auth flow (expired token, transparent to user):**
|
||||||
|
1. Client sends request with expired token
|
||||||
|
2. Server returns `Unauthenticated` + `ReauthChallenge{nonce, timestamp}`
|
||||||
|
3. Client signs the server-provided nonce+timestamp with SSH key
|
||||||
|
4. Client calls `Authenticate` with the signature
|
||||||
|
5. Server verifies, issues new JWT
|
||||||
6. Client caches new token to disk
|
6. Client caches new token to disk
|
||||||
7. Client retries the original request with the new token
|
7. Client retries the original request with the new token
|
||||||
|
|
||||||
### SSH Key Signing (Fallback)
|
This saves a round trip compared to full re-auth — the server provides
|
||||||
|
the nonce, so the client doesn't need to generate one and hope it's
|
||||||
|
accepted. The server controls the challenge, which also prevents any
|
||||||
|
client-side nonce reuse.
|
||||||
|
|
||||||
Used only during the `Authenticate` RPC. The client signs a challenge
|
**Full auth flow (no valid token, transparent to user):**
|
||||||
payload to prove possession of an authorized SSH private key.
|
1. Client has no cached token or token is completely invalid
|
||||||
|
2. Client calls `Authenticate` with a self-generated nonce+timestamp,
|
||||||
|
signed with SSH key
|
||||||
|
3. Server verifies, issues JWT
|
||||||
|
4. Client caches token, proceeds with original request
|
||||||
|
|
||||||
|
### SSH Key Signing
|
||||||
|
|
||||||
|
Used during the `Authenticate` RPC to prove possession of an authorized
|
||||||
|
SSH private key. The challenge can come from the server (re-auth fast
|
||||||
|
path) or be generated by the client (initial auth).
|
||||||
|
|
||||||
**Challenge payload:** `nonce (32 random bytes) || timestamp (big-endian int64)`
|
**Challenge payload:** `nonce (32 random bytes) || timestamp (big-endian int64)`
|
||||||
|
|
||||||
**Metadata fields on Authenticate RPC:**
|
**Authenticate RPC request fields:**
|
||||||
- `x-sgard-auth-nonce` — base64-encoded 32-byte nonce
|
- `nonce` — 32-byte nonce (from server's ReauthChallenge or client-generated)
|
||||||
- `x-sgard-auth-timestamp` — Unix seconds as decimal string
|
- `timestamp` — Unix seconds
|
||||||
- `x-sgard-auth-signature` — base64-encoded SSH signature
|
- `signature` — SSH signature over (nonce || timestamp)
|
||||||
- `x-sgard-auth-pubkey` — SSH public key in authorized_keys format
|
- `public_key` — SSH public key in authorized_keys format
|
||||||
|
|
||||||
**Server verification:**
|
**Server verification:**
|
||||||
- Parse public key, check fingerprint against `authorized_keys` file
|
- Parse public key, check fingerprint against `authorized_keys` file
|
||||||
|
|||||||
Reference in New Issue
Block a user