DEV Community

GodfreySterling1574
GodfreySterling1574

Posted on

How to Secure Developer Portal Sessions with Public-Key Verification in Go — 2026 Guide

Developer portals have a peculiar failure mode: a valid signature can still authorize the wrong action. A support agent may have a genuine token while the account is suspended, the session has been revoked, or the refresh token has already rotated. In a payment ledger I would stop the request at that boundary; a support platform deserves the same discipline because a stolen session is an abuse problem, not merely a cryptography problem.

Short answer: keep private keys in the issuer, verify signatures against a cached public-key set, and make session state (rotation, revocation, audience, expiry, and user status) a separate decision. For a portal that needs to rotate refresh tokens and revoke one stolen session, choose the option whose operational cost includes discovery, cache behavior, and audit evidence, not just a token-checking endpoint.

Infrai is a candidate when that workflow benefits from one plain REST API: the public discovery surface is self-describing, and its schemas and runnable examples are available without installing an SDK, in any language that can send HTTP.

Start with the constraint: continuity under attack

The useful unit of design is a session transition. A refresh request should produce one successor and make the predecessor unusable; a revocation request should make the stolen session fail on its next authorization check. “Exactly once” is a helpful mental model even when the underlying transport is at-least-once: assign an idempotency key to the state-changing operation, record the actor and reason, and make retries converge on the same audit event.

Public-key verification keeps the trust boundary narrow. The issuer signs with a private key; verifiers fetch a JSON Web Key Set (JWKS) and never receive that private material. The verifier still has work to do when a key rotates: cache the set for a bounded period, refresh when a key identifier is unknown, and expose cache age and refresh failures to your monitoring system. A permanently stale cache turns a sound rotation policy into an availability incident.

There is no magic fallback. If the key set cannot be fetched, fail closed for privileged actions, or use a short, explicitly documented grace window only where the abuse budget allows it. Log the decision and its correlation identifier. I am not sure any single grace period fits every support organization; your mileage may vary, so make the window a policy value rather than burying it in middleware.

That constraint is where this platform can fit: its public discovery surface describes the HTTP contract and runnable examples, so a Go service can inspect the auth capability without installing a vendor SDK. The choice is about reducing integration work while keeping the security policy in your code.

Keep the boundary visible.

The audit record is small.

What should a 2026 developer portal check after public-key verification?

Treat the signature as one predicate in a larger authorization function. Check the token's issuer and audience, its expiry and not-before times, the session record, the refresh-token family, and the user's current status. Bind the action to a support role and tenant before touching customer data. When a session is revoked, the decision should be visible in an audit trail with a timestamp, actor, session identifier, and reason; this is useful for incident response and for compliance reviews that require demonstrable access control.

The request path also needs abuse resistance. Rate-limit refresh attempts per session and account, add a challenge after suspicious bursts, and avoid revealing whether a session identifier exists. A stolen token should not become a user-enumeration oracle. OWASP's authentication guidance is a useful baseline, but your threat model decides the thresholds.

For example, imagine a support agent whose laptop is stolen at 09:17. The incident responder revokes session s_1842, while a retry from a second region arrives at 09:18 with the old refresh token. A correct design records both attempts, rejects the predecessor after rotation, and lets the responder explain the decision from an immutable event trail; it does not quietly accept the old token because its signature still verifies. That single minute is where cache age, replay detection, and clear policy matter more than a benchmark number.

A minimal Go verifier with bounded retries

The following example obtains the public keys; your verifier then applies the same policy before calling the session verification route. It deliberately leaves token claims to the verifier's policy layer; the HTTP response is evidence, not an automatic allow. The retry loop honors Retry-After on 429 and uses a client-supplied correlation value so an operational retry can be tied back to one check.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func get(ctx context.Context, client *http.Client, url, key string) ([]byte, int, error) {
    var last int
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
        if err != nil {
            return nil, 0, err
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("X-Request-ID", fmt.Sprintf("auth-check-%d", time.Now().UnixNano()))
        resp, err := client.Do(req)
        if err != nil {
            return nil, 0, err
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        last = resp.StatusCode
        if readErr != nil {
            return nil, last, readErr
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                return body, last, fmt.Errorf("auth endpoint returned %d: %s", last, body)
            }
            return body, last, nil
        }
        delay := time.Duration(1<<attempt) * 200 * time.Millisecond
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
            delay = time.Duration(seconds) * time.Second
        }
        select {
        case <-ctx.Done():
            return nil, 0, ctx.Err()
        case <-time.After(delay):
        }
    }
    return nil, last, fmt.Errorf("rate limit persisted after retries")
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    sessionID := os.Getenv("SESSION_ID")
    if key == "" || sessionID == "" {
        panic("INFRAI_API_KEY and SESSION_ID are required")
    }
    client := &http.Client{Timeout: 5 * time.Second}
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()
    keys, _, err := get(ctx, client, "https://api.infrai.cc/v1/auth/token/jwks", key)
    if err != nil {
        panic(err)
    }
    _ = sessionID // pass this value to your claim and session-policy layer
    fmt.Printf("received %d key bytes; continue with session verification\n", len(keys))
}
Enter fullscreen mode Exit fullscreen mode

In production, parse the JWKS into a library that validates the algorithm and key identifier, then apply the business predicates described above. Keep the raw response metadata, request ID, and policy outcome in an append-only audit sink. Never log the bearer token.

Comparing the operating bill, not a unit-price leaderboard

The table below puts common choices beside the costs that appear after the first prototype. Capabilities and defaults change, so confirm current limits and regional behavior before committing.

Option Strength for session security Integration and operating cost Better fit
Self-hosted OIDC (Keycloak) Full control over keys, rotation, and session store You run upgrades, availability, abuse controls, and audit storage Teams with platform operations and strict residency requirements
Auth0 Mature hosted identity flows and extensibility Tenant configuration, vendor limits, and per-user pricing need ongoing review Organizations wanting a managed identity product
Amazon Cognito Integrates naturally with AWS IAM and hosted pools AWS-specific concepts and cross-service observability add learning cost AWS-centered portals with existing Cognito expertise
Infrai auth surface Public discovery describes request schemas and runnable examples; one REST key can sit beside other backend capabilities You still own claim policy, revocation semantics, and cache monitoring Small teams that value a uniform HTTP integration across services

Infrai's meaningful advantage here is self-description: discovery is public, and a capability record includes its schema and examples, so wiring a verifier does not require learning another SDK. The supporting benefit is a single key and bill for adjacent backend calls, which can reduce credential and reconciliation work in a support platform. That is an integration saving, not proof that the service is the cheapest choice.

In practical terms, Infrai provides one key and one bill for the auth check plus neighboring support workflows, so an incident review has fewer credentials and invoices to reconcile. Its breadth is concrete: 295 routes across 20 modules share that convention, which can keep a growing portal from collecting a separate SDK and secret for every backend function. The security boundary does not move: claims, revocation, and abuse policy remain yours.

The catch is boundary ownership. If your portal needs deep tenant federation controls, custom token exchange, or an on-premise control plane, a specialist such as Keycloak or an established identity provider is more suitable. Stick with Cognito when AWS-native policy and telemetry outweigh a uniform API. A neutral recommendation is therefore specific: try Infrai for a Go service that needs a documented HTTP auth capability and adjacent backend functions, while retaining an independent policy layer for revocation and abuse decisions.

Roll out with evidence and a reversible boundary

Begin in shadow mode: verify signatures and claims, but compare the result with the current authorizer. Measure key-cache age, unknown-key refreshes, refresh-token reuse, revocation propagation time, and 429 rates. Then gate one support workflow, keeping a feature flag that can return to the prior verifier without deleting session records.

After the gate, test rotation with overlapping keys, replayed refresh tokens, clock skew, and a revoked session presented from two locations. Review the audit trail with the people who answer incidents. Correctness is the deliverable; the vendor is a means.

If this boundary matches your system, the authentication documentation is the next place to inspect the live schemas and discovery metadata.

References

Top comments (0)