One Identity, Many Products: Engineering Monesize Gateway
![]() |
| Monesize Gateway |
A business using three products should not have to manage three unrelated identities for the same employee. An employee leaving the organization should not require an administrator to remember every product the employee used and manually revoke access in each one. The more products a business adopts, the more fragmented identity and organizational membership become.
Monesize therefore needs a shared identity layer without turning the individual products into one application. This is where Monesize Gateway comes in.
Gateway is the identity, organization, and access foundation that sits across the Monesize ecosystem. It gives every person one canonical Monesize identity and gives every business one authoritative organizational membership. The products remain independent. Gateway provides the shared foundation that allows them to operate as parts of the same ecosystem.
The important architectural distinction is that Gateway does not centralize everything. It centralizes only what genuinely belongs at the platform level.
The Core Design Principle
The most important decision in designing Gateway was defining what it should own and, just as importantly, what it should leave to the products.
Gateway answers two platform-level questions: who is this person, and does this person belong to this organization? It does not determine what that person is allowed to do inside a particular product. Product-level authorization remains the responsibility of the product itself.
An accountant in Core has finance permissions that are specific to Core. A support agent in Desk has ticket permissions that are specific to Desk. Gateway does not need to understand either permission model. It only needs to establish that both people have verified Monesize identities and belong to their respective organizations.
This boundary keeps Gateway useful as the ecosystem grows. A centralized authorization system would require every new product to define its permission model inside Gateway. That would make Gateway increasingly coupled to the implementation details of every product it serves.
However, by restricting Gateway to identity and organizational membership, new products can integrate with the platform without modifying Gateway to understand their internal authorization rules. Gateway establishes the relationship. Each product decides what that relationship means within its own domain.
This "separation of power" is one of the most important architectural properties of the system.
The Identity Model
Every person who uses a Monesize product that integrates with Gateway has one Monesize account. The account contains the person's name, email address, and credentials. It does not contain product-specific permissions or a list of everything the person is allowed to do across the ecosystem. It is an identity record.
Gateway currently supports three authentication mechanisms around that identity. Email and password authentication uses bcrypt hashing at a cost factor designed to resist brute-force attacks. Google OAuth and Microsoft OAuth provide alternative authentication paths for businesses that prefer social sign-in. Two-factor authentication via TOTP is available for email and password accounts, while OAuth providers remain responsible for their own multi-factor enforcement.
Sessions are issued as signed JWTs and stored in HTTP-only cookies. Gateway uses RS256 asymmetric signing so downstream services can verify Gateway-issued tokens using the public key without requiring access to the private signing key.
The session contains the user ID, session ID, and a token version. The token version provides the revocation mechanism. When the version is incremented in the database, existing sessions tied to that account become invalid on the next request.
Gateway also stores sessions in the database rather than relying entirely on JWT expiration. This provides hard revocation. Logging out, changing a password, or deactivating an account can invalidate sessions immediately instead of waiting for an otherwise valid token to expire.
That distinction matters for a shared identity system. Authentication is not just about issuing a token. The platform also needs a reliable way to withdraw trust that has already been issued.
Organizations as First-Class Entities
The organization model follows the same separation between platform concerns and product concerns.
A Gateway organization is not simply a property of a product tenant. It is an independent entity that exists at the Gateway level. A business establishes its organization once and can then connect its Monesize product tenants to that organizational identity.
This allows organizational membership to remain independent from product access. Being a member of an organization does not automatically grant access to every product tenant operated by that organization. Each product remains responsible for deciding what the relationship permits within its own domain.
Gateway currently has three organization membership roles. Owner has full control, including organization deletion. Admin can manage members and tenants. Member has read access to the organization.
Role assignment is validated against the database on every relevant operation rather than being trusted from a token. This means a role change takes effect immediately without waiting for an existing token to expire.
Every significant action against an organization also produces an immutable audit event. Audit records are never updated or deleted. This gives administrators a durable record of who changed what and when.
The organization therefore becomes the authoritative platform-level relationship, while product tenants remain responsible for product-level access.
Sign In With Monesize
Once identity exists at the platform level, the next problem is allowing that identity to move between independent products without forcing each product to implement its own account creation and authentication experience.
Sign In With Monesize provides that connection.
The flow allows a person to use their Gateway identity to authenticate into a connected Monesize product without creating a separate account through that product's login interface.
The implementation uses a browser redirect flow combined with a server-side token exchange. When a user selects Sign In With Monesize on a product's login page, the product redirects the browser to a Gateway authorization endpoint. Gateway checks whether the user already has an active session.
When an active Gateway session exists, Gateway generates a short-lived, single-use exchange token and redirects the browser back to the product with that token in the URL. The product backend then calls a Gateway service-to-service endpoint, presents the exchange token, and receives the user's identity claims.
The exchange token is signed using RS256, expires after five minutes, and can only be consumed once. Gateway hashes the token on receipt and marks it as consumed. The product then uses the returned identity claims to find or create its local user record and establish its own session.
The decision to use token exchange rather than shared session cookies is intentional. Independent products should not depend on sharing session state. Each product owns its own session management, while Gateway provides a trusted identity assertion that the product can consume.
This preserves product independence while still allowing the user experience to feel connected.
Tenant Linking and the Webhook Trust Model
Connecting products to organizations introduces another platform-level requirement: Gateway needs a secure way to communicate with independently operated product tenants when organizational membership changes.
For Core tenants, Gateway generates a random webhook secret when an organization links a tenant. The secret is encrypted using AES-256-GCM before being stored. The plaintext secret is shown to the administrator once during linking and is not subsequently recoverable from Gateway.
That decision is deliberate. The Gateway database should not become a source from which plaintext integration secrets can be recovered.
The webhook secret serves as the trust anchor for revocation webhooks. When Gateway needs to revoke a user in a Core deployment, it signs the request body using the secret with HMAC-SHA256. Core verifies the signature before accepting and acting on the request.
The result is a trust relationship between Gateway and each independently deployed Core instance without requiring the Core deployment to expose its credentials or participate in shared infrastructure.
Cross-Product Revocation
Identity federation is only useful when changes to identity and organizational membership propagate correctly.
Removing a person from a Gateway organization therefore has consequences beyond Gateway itself. That removal needs to reach every connected product and tenant through which the person had access because of that organizational relationship.
Gateway uses two mechanisms for this, based on how the products are deployed.
For multi-tenant products such as Desk, Engage, and Books, Gateway publishes a structured revocation event to a shared Redis channel. Each product runs a subscriber worker that listens for these events.
When a product receives a revocation event, the worker finds the relevant user within the appropriate tenant, deactivates the user's membership, and increments the user's token version. The product's authentication middleware already checks token version on every request, so an existing session becomes invalid on the user's next API call.
For Core, the architecture is different. Core is single-tenant and independently deployed for each customer, so those deployments cannot rely on a shared Redis infrastructure. Gateway therefore uses the signed webhook mechanism described earlier.
Gateway calls the registered webhook URL for each linked Core deployment with an HMAC-signed payload identifying the user whose access needs to be revoked. Failed webhook calls are retried three times with exponential backoff at five seconds, thirty seconds, and two minutes. Every attempt and its outcome is written to the organization's audit log.
The different mechanisms are a consequence of the deployment models. Redis pub/sub works well for multi-tenant products because a single publication can reach all active subscribers regardless of how many product instances are running. Webhooks are more appropriate for independently operated Core deployments because those instances cannot participate in a shared internal messaging infrastructure.
The revocation operation itself is also deliberately asynchronous. Gateway marks the membership as removed synchronously so the organizational state changes immediately. The downstream revocation events are then published asynchronously rather than making the administrator wait for every connected product to respond.
This keeps the platform responsive while still providing a mechanism for propagating the access change.
Plan Enforcement
Gateway also owns subscription-level capabilities that apply to the platform itself.
Free organizations can have up to three members, cannot link product tenants, and can view only the most recent thirty days of their audit log. Premium organizations have no such limits.
These restrictions are enforced in the service layer rather than relying on frontend state. Every operation involving a gated capability checks the organization's subscription status directly against the database before proceeding.
The frontend can surface contextual upgrade prompts, but those prompts are not the enforcement mechanism. The backend remains authoritative because the frontend is untrusted and any restriction enforced only through the interface can be bypassed by calling the API directly.
Keeping subscription enforcement in the service layer also keeps the policy close to the operations it governs. Gateway can therefore make a decision based on the organization's current subscription state rather than trusting information that may have been stale or manipulated on the client.
Rate Limiting Without Punishing Shared Networks
Authentication rate limiting introduces a different problem when the platform is designed for businesses rather than isolated individual users.
A conventional implementation often keys authentication limits by IP address. That works reasonably well for individual users, but it creates problems for organizations that route employees through a shared VPN or corporate proxy. Ten employees logging in from the same public IP address could collectively exhaust the limit and prevent an eleventh employee from authenticating.
Gateway therefore uses different rate-limit keys for different authentication operations.
Login and forgot-password limits are keyed by email address rather than IP. An attacker attempting to brute-force a specific account is still subject to a fixed limit against that account, while employees sharing a corporate IP remain independently rate-limited because they are authenticating against different email addresses.
The session-check endpoint uses the user ID extracted from the session token as its rate-limit key. That endpoint is called with an established session, so the user identity is already available and provides a more meaningful boundary than the network address.
Implementing this required a custom key generator in the rate-limiting middleware rather than using the default IP-based mechanism.
The code change is relatively small. The behavioral difference is much more important because the system is designed for organizational users operating through shared networks.
What I Would Do Differently
Gateway is already solving the core platform problems it was designed to address, but there are areas where the implementation can be strengthened.
The webhook secret used for Core tenants currently has no dedicated rotation mechanism. Once a secret is compromised, the current remediation is to unlink and relink the tenant, which generates a new secret but also disrupts the integration. A dedicated rotation endpoint would provide a cleaner solution by generating a replacement secret, returning it once, and updating the encrypted value stored by Gateway.
The audit model is also currently scoped to individual organizations. Gateway does not yet expose a cross-organization audit capability. That would become relevant if Monesize needed to investigate activity across the platform for compliance or operational purposes. The underlying data model already records an actor user ID on each audit event, but the query and presentation layers currently expose organization-level views only.
There is also a consequence of the current Sign In With Monesize flow that is worth addressing. When a product receives a valid Gateway identity and does not already have a corresponding local user, it creates one. That makes initial adoption straightforward, but it also creates the possibility of orphaned product records when a Gateway identity is subsequently deactivated.
The natural next step is to connect the identity deactivation path more directly to product-level sessions and local records after the Redis revocation infrastructure is established across the multi-tenant products.
These are not reasons to change the fundamental architecture. They are areas where the existing architecture can be extended as the platform matures.
On a Ending Note...
Monesize Gateway exists because Monesize now owns multiple products.
The products solve different business problems and need to remain independently deployable, independently useful, and independently responsible for their own domain-specific authorization. At the same time, the people and organizations using those products should not have to experience that independence as fragmentation.
Gateway provides the platform-level foundation that bridges those two requirements. It provides one verified identity, one authoritative organizational relationship, and a mechanism for propagating changes to that relationship across connected products.
The hardest part of building Gateway was therefore not implementing authentication, JWTs, Redis events, webhooks, or token exchange. It was deciding what the central layer should not own.
Every capability that could have been pulled into Gateway had to be evaluated against the same question: does this genuinely belong to the platform, or can the individual product handle it correctly on its own?
Product-specific authorization belongs in the products. Product sessions belong in the products. Product business logic belongs in the products. Gateway owns the shared identity and organizational relationships that those products cannot reasonably maintain independently without creating fragmentation across the ecosystem.
That boundary is what allows Monesize to grow as a product suite without gradually turning into a tightly coupled monolith.
Gateway is therefore not another Monesize product sitting alongside Core, Desk, Engage, or Books. It is the infrastructure that allows those products to remain separate while still behaving like parts of the same system.
.png)
Comments
Post a Comment