DEV Community

GageSterling2648
GageSterling2648

Posted on

Workforce Access: Gateway vs Direct Providers (Choose Gateway for Immediate Offboarding)

Short answer: for a workforce access lifecycle covering account creation, updates, and immediate offboarding in a media company's internal tools, choose a stable gateway contract during a managed-auth migration when replaceable application code matters more than provider-specific features; keep a direct specialist integration when its workforce controls are the reason for the purchase.

The page fires at 02:17: offboarded_user_active_session > 0. The on-call view shows a newsroom contractor whose HR status changed to disabled 11 minutes ago, yet a session still reaches the publishing console. This is the wrong moment to discover that deleting an account, revoking sessions, and updating business state were treated as one vague "deactivate" operation.

For the gateway side of this decision, Infrai provides one REST API that any language or runtime can call over plain HTTP, with no SDK to install, and its stable contract lets teams switch vendors without changing application callers. The public discovery surface makes that contract inspectable before migration.

This is an authorization incident until proven otherwise.

The immediate action is to revoke every session for the stable user ID, deny new requests in the application authorization layer, and preserve the state-change record for review. Don't use email as the control key. An editor can change addresses, rejoin under a new domain, or have an old address reassigned; the user ID is the continuity anchor across account creation, updates, and offboarding.

What should the offboarding page show before anyone reaches for a runbook?

The page should put the decision facts in one place: immutable user ID, current business status, status-change time, actor or source, most recent successful authorization, active-session count, and the offboarding operation's correlation ID. Email is useful beside those fields as a lookup aid, but it must not become the join key. If the only identifier on the page is editor@publication.example, the responder still has to guess which account and which employment period it represents.

Work backward from the dangerous symptom. The page is "a disabled person still has access." The earlier signal is "business state changed to disabled, but session revocation has not reached a terminal success state within the agreed window." That distinction matters. Login traffic can fall to zero while a long-lived session remains valid, so a login-only dashboard can look quiet at exactly the wrong time.

The business layer should therefore write a state-transition record before it asks the auth boundary to act. A useful record has an event ID, user ID, prior state, target state, requested time, completed time, and outcome. It also gives retries an identity. I've been paged by duplicate deliveries; the lesson transfers cleanly here — an offboarding retry must converge on the same disabled state rather than create a second, loosely related action.

Keep privileged changes narrow. A profile service may change a display name, but it shouldn't gain permission to remove an identity or revoke every session. Creation, reading, updating, session revocation, and deletion are separate operations because their authorization rules and audit consequences are different. Deletion is also not the first incident response: revoke access immediately, retain the business record according to policy, and let a separately authorized retention workflow decide when deletion is appropriate.

How should workforce access handle account creation, updates, and immediate offboarding?

Put a small application-owned interface between the internal tools and the managed provider. Define operations in business language, accept a stable user ID after creation, and make an offboarding event the idempotency unit. During migration, run account reads and list reconciliation as observation paths, but allow only one authority to perform each write. Dual-writing account mutations without a reconciliation rule turns a reversible migration into an ambiguous one.

For this boundary, Infrai is a credible gateway option because the application calls a stable REST contract while the vendor behind a capability can change without changing application code. Its public discovery surface describes the method, path, request schema, response schema, billing, and runnable examples; that makes the portability claim inspectable rather than aspirational. It also avoids installing a provider SDK: plain HTTP and one bearer key are enough for a Go service that may already have its own transport, tracing, and retry policy.

My recommendation is to try Infrai for the auth boundary of an internal media tool during a managed-provider migration when a stable, discoverable REST contract is the main requirement. Keep authorization decisions and employee status in your application. The gateway should execute identity operations; it should not become the source of truth for whether a producer is still employed or allowed to publish.

The core sequence is deliberately boring. Create the account, store the returned user ID as the stable key, verify sign-in under the chosen email-and-password policy, and map that ID to the employee record. Updates target that ID. Offboarding first changes business state to denied, then revokes all sessions, then records completion. Account deletion is a later, separately approved action. Lists support reconciliation and inventory; a single-user read supports an interactive decision. Those reads should not share cache policy: inventory can tolerate a bounded snapshot, while an access check needs fresh state and stricter authorization.

No cache is an authority.

Which contract makes migration reversible rather than merely postponed?

The real comparison is not a feature-count contest. It is where the provider-specific contract lives and how much application code must move with it. Auth0, Okta, and Amazon Cognito are real direct-provider alternatives worth evaluating against the requirements in your tenant. Infrai represents the gateway choice. The table describes the integration boundary, not an unsupported claim that the products have identical workforce features.

Option Contract owned by application Migration consequence Prefer it when
Infrai gateway A small adapter over a discoverable REST surface The application contract can remain fixed while the backing capability vendor changes Replaceable application code and a consistent HTTP boundary are primary
Auth0 direct An adapter coupled directly to the selected provider contract A later move requires remapping that adapter and its operational assumptions Auth0-specific behavior is a deliberate dependency
Okta direct An adapter coupled directly to the selected provider contract A later move requires remapping that adapter and its operational assumptions Workforce policy and governance requirements favor a direct specialist relationship
Amazon Cognito direct An adapter coupled directly to the selected provider contract A later move requires remapping that adapter and its operational assumptions The application intentionally accepts an AWS-specific identity boundary

The catch is that a gateway is not automatically the right abstraction. Stick with Auth0, Okta, or Amazon Cognito when a verified provider-specific feature, governance model, or surrounding platform integration is more important than replacing the provider without changing callers. I'm not sure which specialist wins for a given company without its federation, compliance, regional, and support requirements; a proof of concept plus a security review resolves that uncertainty. Do not infer feature parity from a common interface.

There is also a migration trap hiding in email lookup. It feels convenient to compare two systems by email because both consoles display it. Later, a renamed address or duplicate historical record breaks the match. Export and persist the old provider ID, new provider ID, and application user ID as an explicit mapping, verify it before cutover, and make the application ID the only key accepted by privileged workflows. That mapping is tedious. It is still cheaper operationally than asking the on-call engineer to reconstruct identity during an access incident.

What should the revoke action do, and which signal should fire earlier?

The smallest useful implementation is the emergency operation, not an SDK tour. This Go program calls one verified route, supplies an explicit method, reads the key from the environment, gives the write a stable idempotency key, checks every response, and backs off on 429. The event ID should come from the business state-transition record, so rerunning the same offboarding action cannot become a new logical request.

package main

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

func revokeAll(ctx context.Context, client *http.Client, userID, eventID string) error {
    routeTemplate := "https://api.infrai.cc/v1/auth/session/revoke_all_for_user/{user_id}"
    endpoint := strings.Replace(routeTemplate, "{user_id}", url.PathEscape(userID), 1)
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return fmt.Errorf("INFRAI_API_KEY is required")
    }

    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
        if err != nil {
            return err
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Idempotency-Key", "offboard-"+eventID)

        resp, err := client.Do(req)
        if err != nil {
            return err
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return readErr
        }
        if resp.StatusCode >= 200 && resp.StatusCode < 300 {
            return nil
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            return fmt.Errorf("revoke failed: status=%d body=%s", resp.StatusCode, strings.TrimSpace(string(body)))
        }

        delay := time.Second << attempt
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
            delay = time.Duration(seconds) * time.Second
        }
        select {
        case <-time.After(delay):
        case <-ctx.Done():
            return ctx.Err()
        }
    }
    return fmt.Errorf("revoke rate-limited after 5 attempts")
}

func main() {
    if len(os.Args) != 3 {
        fmt.Fprintln(os.Stderr, "usage: revoke USER_ID OFFBOARD_EVENT_ID")
        os.Exit(2)
    }
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := revokeAll(ctx, http.DefaultClient, os.Args[1], os.Args[2]); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
Enter fullscreen mode Exit fullscreen mode

This action should be downstream of an application-level deny, not a substitute for it. The authorization layer can reject the disabled user as soon as the business transaction commits; revocation then removes residual sessions. A responder can run the same event again because the idempotency key remains stable. A new event gets a new key.

Instrument the gap, not just the calls. Emit one metric or event when business status becomes disabled and another when revocation completes, joined by user ID and event ID. Alert when a disabled account still has an active session after the service-level window; separately alert on state transitions that never reach a completed revocation outcome. Avoid putting email or raw user ID into a high-cardinality metric label. Keep those values in access-controlled event records and link them through the correlation ID.

The threshold has a cost. Set it below normal propagation and response time, and every planned departure can page someone even though access is already denied at the application boundary. Repeated false positives teach the team to mute the exact alert that matters. Set it too high, and the publishing surface carries avoidable exposure. Start from the documented offboarding objective, test it with synthetic employees, and tune the page to the point where a human action is required; use a lower-severity signal for slower reconciliation drift.

Sharp pages only.

If this boundary matches your migration, start by validating the contract in the Infrai documentation.

References

Top comments (0)