DEV Community

Cover image for The OAuth2 Integration That Was Never Going to Work
Son Do
Son Do

Posted on

The OAuth2 Integration That Was Never Going to Work

MyCase Immigration / Docketwise Integration: 0: Why We Synced · 1: Before/After · 2: OAuth2 · 3: Sync vs Migration · 4: Sync Boundary · 5: Family Graph · 6: Write Patterns · 7: Webhooks · 8: Billing

The OAuth2 integration between MyCase and Docketwise existed before we started the project. It had been built as a proof-of-concept during early partnership discussions. It was not in production. We inherited it.

The grant model was backwards.

This post explains what was wrong with it, why the problem was structural and not implementational, and what model inversion required at the OAuth2 level.


What the original integration did

In the original design, Docketwise was the authorization server. When a MyCase user wanted to access their Docketwise immigration data, the flow was:

  1. MyCase redirected the user to Docketwise's OAuth2 authorization endpoint.
  2. The user authenticated with their Docketwise credentials.
  3. Docketwise issued an access token.
  4. MyCase used that token to make API calls to Docketwise on the user's behalf.

The model treated Docketwise as the identity authority. A MyCase user had to have a Docketwise account. If Docketwise's auth service was unavailable, no MyCase user could access immigration features regardless of whether they were already logged into MyCase.

This is the standard OAuth2 authorization code flow, and it works fine when the authorization server is the primary system. The problem was that Docketwise was not the primary system here. MyCase was.


Why the hosting relationship determines auth authority

Consider what "hosting" means in this integration. MyCase is the product the attorney logs into every morning. It handles billing, communications, and practice management. Docketwise is surfaced inside MyCase as an immigration module. An attorney using "MyCase Immigration, powered by Docketwise" opens MyCase. Docketwise is embedded within that experience.

The deployment topology is: MyCase is the host, Docketwise is the engine.

OAuth2 authorization authority follows the host. When a user is already authenticated to MyCase, demanding that they authenticate separately to Docketwise before accessing immigration features creates two separate sessions that have to stay in sync. It means that a MyCase user with no Docketwise account cannot use immigration features even if their firm has a Docketwise subscription. It means that Docketwise token expiry has a direct effect on MyCase session state.

In the original design, Docketwise's availability was a dependency for MyCase authentication. An auth service outage in Docketwise would prevent MyCase users from accessing any Docketwise-backed features, even though their MyCase session was valid. For a product that positions itself as one unified experience, that coupling was unacceptable.

The fundamental issue: the integration treated Docketwise as the authority over an authentication relationship that MyCase owned.


What model inversion required

Inverting the model meant making MyCase the authorization server and Docketwise the resource server.

In the corrected design:

  1. MyCase users authenticate to MyCase using their existing credentials. No separate Docketwise login.
  2. When a MyCase user accesses immigration features, MyCase's OAuth2 server issues a token representing that user's MyCase identity.
  3. The MyCase Rails backend sends that token in API requests to Docketwise.
  4. Docketwise validates the token against MyCase's token introspection endpoint, confirming it is a valid MyCase-issued token.
  5. Docketwise trusts that MyCase has authenticated the user and authorized the access.

Corrected model: MyCase as authorization server, Docketwise as resource server. User authenticates once to MyCase. MyCase backend calls Docketwise API with tokens issued by MyCase. Docketwise validates against MyCase token introspection endpoint.

Docketwise's FastAPI authentication middleware was rewritten to call MyCase's token introspection endpoint on each request. Docketwise stopped issuing tokens for this integration entirely. It became a resource server in the OAuth2 sense: it validates tokens it receives and trusts the issuer.


Why this could not be fixed by implementation changes

The original integration's problem was not a bug. The authorization server was correctly implemented. The grant flow was correctly implemented. But the choice of which system was the authorization server was wrong, and that choice cannot be fixed by tuning the implementation.

Changing the implementation of a backwards authorization model still gives you a backwards authorization model with a better implementation. The correct fix was to change the model.

The principle: the authorization server is the system that manages user identity for the combined product. If one system is embedded inside another as a feature module, the embedding system manages identity and issues tokens. The embedded system validates those tokens. Reversing this makes the embedded system a dependency for the host system's auth, which creates availability coupling that does not belong there.

In practice, model inversion also changed account provisioning. Under the original model, a MyCase user who activated immigration features needed an existing Docketwise account or had to create one. Under the inverted model, Docketwise account provisioning happens when the MyCase firm activates the Immigration addon. The attorney never sees Docketwise credentials. The Docketwise account exists to carry the data; the MyCase account is the identity.


What stayed the same

The Docketwise standalone product did not change. Attorneys using Docketwise on its own still authenticate directly to Docketwise using Docketwise credentials. The model inversion only applies to the MyCase-hosted integration.

Docketwise's API itself did not change for this. Adding token validation against MyCase's introspection endpoint was a middleware change to Docketwise's FastAPI authentication layer. The API routes, the data model, and the business logic were untouched. The change was: where does the token come from, and who validates it.

This is the first structural decision in the series. The rest follow from it. The integration could be built with MyCase as the platform host only after the auth relationship correctly reflected that topology.

OAuth2 model inversion: Docketwise FastAPI middleware validates tokens against MyCase's introspection endpoint; MyCase acts as the authorization server for the integrated product while the standalone Docketwise auth path remains unchanged


Next: Part 3. Why We Chose Permanent Sync Over Migration. The migration option sounds compelling, export the Docketwise data, import it to MyCase, done. This post covers why that argument falls apart when you look at what Docketwise data actually is.

Top comments (0)