Skip to content

Challenge-Response Wallet Session Token Generation Sub-Module #215

Description

@JamesEjembi

Problem Statement

Authenticate node administrative personnel passwordlessly. The current session management relies on shared API tokens that are long-lived, stored in plaintext configuration files, and cannot be revoked granularly. A compromised token gives an attacker full administrative access until manual rotation. Node operators need a passwordless authentication flow that uses their existing Stellar wallet keypair to sign a server-generated challenge, proving control of the identity without revealing the secret key.

Technical Bounds & Invariants

  • Challenge nonce: 32 cryptographically random bytes, single-use, expires after 5 minutes
  • Wallet signature scheme: Ed25519 (Stellar native); signature is 64 bytes
  • JWT lifetime: 1 hour with refresh token (7 days, single-use rotation)
  • Stellar StrKey encoding: uses G... prefix for public keys (base32, 56 chars)
  • Challenge must be bound to client IP and User-Agent to mitigate token theft

Codebase Navigation Guide

  • Primary target: /src/api/auth/challenge.ts
  • Session middleware: /src/api/auth/session.ts — verifySession() at line 30
  • JWT manager: /src/api/auth/jwt_manager.ts
  • Stellar SDK key utils: /node_modules/@stellar/stellar-sdk/lib/keypair.js

Step-by-Step Resolution Blueprint

  1. Implement POST /api/v1/auth/challenge that generates a random 32-byte nonce via crypto.randomBytes(32), stores (nonce, ip, user_agent, expires_at) in Redis with 5-minute TTL (key: challenge:{nonce_hex}), and returns { nonce: base64(nonce), serverId: "VeriNode-Backend" }
  2. Implement POST /api/v1/auth/verify that accepts { nonce, publicKey, signature }; verify: (a) nonce exists in Redis and not expired, (b) IP matches stored IP, (c) publicKey matches G... valid StrKey format (verify using StrKey.isValidEd25519PublicKey), (d) verify the Ed25519 signature of SHA256(nonce || serverId) using Keypair.fromPublicKey(publicKey).verify()
  3. On successful verification: delete the nonce from Redis (prevent replay), issue a JWT access token (1h) and a JWT refresh token (7d) signed with a server-side RS256 key pair; store the refresh token hash in Redis (key: refresh:{token_hash}, TTL: 7d)
  4. Implement POST /api/v1/auth/refresh that accepts { refreshToken }, verifies it against the stored hash, issues a new access token and a rotated refresh token (single-use rotation), and invalidates the old refresh token hash
  5. Add a SessionMiddleware that extracts the JWT from the Authorization: Bearer <token> header, verifies the signature using the public RSA key, checks expiry, decodes the node_id claim, and attaches it to req.nodeId for downstream handlers
  6. Add rate limiting on the challenge endpoint (10/ip/minute) and verify endpoint (5/ip/minute) to prevent brute-force enumeration of valid nonces
  7. Write integration tests using @stellar/stellar-sdk to: (a) generate a random keypair, (b) complete the challenge/verify flow, (c) use the JWT to call an authenticated endpoint, (d) verify refresh token rotation

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Complexity: HardcoreExtremely difficult, high-complexity engineering taskGrantFox OSSIssue tracked in GrantFox OSSLayer: InfrastructureInfrastructure layer architectural concernMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official CampaignThird CampaignCampaign: Third CampaignType: Core-ArchitectureCore system architecture change required

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions