feat: add jwt_es256 auth type for signed JWT APIs#47
Merged
donna-matt merged 4 commits intomainfrom Mar 30, 2026
Merged
Conversation
Buffer.from().buffer returns the entire pooled ArrayBuffer (8KB) instead of just the key bytes, causing crypto.subtle.importKey to fail. Also made PEM parsing more tolerant of varying dash counts and escaped newlines, and added error handling for descriptive JWT error messages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donna-matt
added a commit
that referenced
this pull request
Apr 1, 2026
- Add webhookKey and webhookSecret columns to tokens table
- New inbound_events table with status/expiry/channel fields
- POST /inbound/{webhookKey}/{channel} — receive webhooks from external services
- GET /inbound/pending — poll pending events (bearer auth)
- POST /inbound/ack/{id} — acknowledge processed events
- DELETE /inbound/purge — admin purge old events
- POST/DELETE/PATCH /api/tokens/{id}/webhook — manage webhook keys
- In-memory rate limiter (100 req/min per key)
- HMAC-SHA256 signature verification (GitHub/Linear/Stripe style)
- Dashboard UI section in api-keys detail page
- Migration: drizzle/0005_inbound_webhook_buffer.sql
- Tests: 16 new test cases covering service logic
donna-matt
added a commit
that referenced
this pull request
Apr 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
New
jwt_es256authentication method type that dynamically generates ES256-signed JWTs for each proxied request.Why
APIs like Apple's App Store Connect require fresh JWT tokens signed with ECDSA P-256 per request, rather than static bearer tokens. Each JWT must be signed using:
kidin JWT header)issin JWT payload)Previously, there was no way to handle this authentication pattern in Shellgate.
How It Works
Backend Implementation
New JWT utility (
src/lib/server/utils/jwt.ts):appstoreconnect-v1audience, 20-minute expirationUpdated auth methods service (
src/lib/server/services/auth-methods.ts):jwt_es256to valid auth typesES256 JWT ••• KEYID123Updated gateway proxy (
src/lib/server/services/gateway.ts):jwt_es256auth method is configuredAuthorization: Bearer <jwt>headerTesting
tests/unit/jwt.test.ts): 7 tests covering JWT generation, signature verification, custom configtests/integration/gateway.test.ts): End-to-end test verifying JWT injection in proxied requestsUsage Example
Creating a Target + Auth Method for App Store Connect
POST /api/targets { "name": "Apple App Store Connect", "slug": "apple-asc", "type": "api", "base_url": "https://api.appstoreconnect.apple.com" }POST /api/targets/{target_id}/auth-methods { "label": "App Store Connect JWT", "type": "jwt_es256", "credential": "{\"privateKey\":\"-----BEGIN PRIVATE KEY-----\\nMIGHAgEAMB...\",\"keyId\":\"ABC123XYZ\",\"issuerId\":\"69a6de12-b0d3-...\"}" }GET /gateway/apple-asc/v1/apps # Shellgate automatically generates and injects fresh JWTCredential Format
The
credentialfield must be a JSON string with:{ "privateKey": "-----BEGIN PRIVATE KEY-----\nMIGH...\n-----END PRIVATE KEY-----", "keyId": "ABC123XYZ", "issuerId": "69a6de12-b0d3-...", "audience": "appstoreconnect-v1", // optional, defaults to appstoreconnect-v1 "expiresInSeconds": 1200 // optional, defaults to 1200 (20 minutes) }Dashboard UI
Dashboard UI for creating/editing
jwt_es256credentials is intentionally not included in this PR. This keeps the PR focused on backend functionality. Dashboard support will be added in a follow-up PR.Security Notes
Breaking Changes
None. This is purely additive — existing auth methods continue to work unchanged.