Complete the OAuth 2.1 authorization flow in Link by implementing Google and Microsoft identity provider callbacks.
The Link service has OAuth 2.1 endpoints for MCP client authentication, but the IdP callback handlers (/oauth/callback/google, /oauth/callback/microsoft) are stubs returning 501 Not Implemented. Users see provider selection buttons but clicking them fails.
| Decision | Choice | Rationale |
|---|---|---|
| Flow state storage | PostgreSQL table | Survives restarts, multi-instance safe, debuggable |
| Cleanup strategy | Hybrid lazy cleanup | Expiry check on read + periodic delete of expired rows |
| Callback pattern | Single endpoint per provider | Detect IdP return by presence of code param |
| Credentials | Reuse existing SSM params | Same Google/Microsoft OAuth apps as ERP |
| Identity model | Must exist in identity table |
Same users and permissions as ERP |
MCP Client Link Service Google/Microsoft
│ │ │
│ GET /oauth/authorize │ │
│─────────────────────────────>│ │
│ │ INSERT oauth_pending_flow │
│ │ │
│ HTML: provider selection │ │
│<─────────────────────────────│ │
│ │ │
│ GET /oauth/callback/google?state=... │
│─────────────────────────────>│ │
│ │ 302 to Google │
│ │─────────────────────────────>│
│ │ │
│ │ User authenticates │
│ │ │
│ │ 302 ?code=...&state=... │
│ │<─────────────────────────────│
│ │ │
│ │ Validate ID token │
│ │ Lookup identity by email │
│ │ Issue authorization code │
│ │ DELETE oauth_pending_flow │
│ │ │
│ 302 redirect_uri?code=... │ │
│<─────────────────────────────│ │
│ │ │
│ POST /oauth/token │ │
│─────────────────────────────>│ │
│ │ │
│ {access_token: ...} │ │
│<─────────────────────────────│ │
CREATE TABLE oauth_pending_flow (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
state_key TEXT NOT NULL UNIQUE,
client_id TEXT NOT NULL,
redirect_uri TEXT NOT NULL,
scope TEXT NOT NULL,
client_state TEXT,
code_challenge TEXT NOT NULL,
code_challenge_method TEXT NOT NULL,
client_name TEXT,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_oauth_pending_flow_expires_at ON oauth_pending_flow(expires_at);
state_key: random key passed through IdP as stateclient_state: MCP client's original state (echoed back unchanged)WHERE expires_at > now() on reads, periodic DELETE of expired;; Top-level shared provider credentials
:com.getorcha/auth-providers
{:google {:client-id #orcha/param "/v1-orcha/cognito-google-client-id"
:client-secret #orcha/param "/v1-orcha/cognito-google-client-secret"}
:microsoft {:client-id #orcha/param "/v1-orcha/microsoft-auth-client-id"
:client-secret #orcha/param "/v1-orcha/microsoft-auth-client-secret"}}
;; ERP - merge provider credentials into :auth :microsoft
:com.getorcha.erp.http/handler
{...
:auth {:microsoft #merge [#ref [:com.getorcha/auth-providers :microsoft]
{:state-secret #orcha/param "/v1-orcha/microsoft-auth-state-secret"}]
...}}
;; Link - refs shared providers
:com.getorcha.link.http/handler
{:base-url #ref [:com.getorcha/link-base-url]
:db-pool #integrant/ref :com.getorcha.db/pool
:aws #integrant/ref :com.getorcha.aws/state
:key-arn #orcha/param "/v1-orcha/oauth-signing-key-arn"
:auth-providers #ref [:com.getorcha/auth-providers]}
Single endpoint per provider, detecting direction by code param:
(defn google-callback-handler [request respond raise]
(let [{:keys [code state]} (-> request :parameters :query)]
(if code
(handle-idp-return ...) ; Google sent us back
(handle-idp-redirect ...)))) ; User clicked button
handle-idp-redirect:
state_keycom.getorcha.oauth.providers.googlehandle-idp-return:
code for tokens with IdPidentity tableerror=access_denied| Scenario | Response |
|---|---|
| Invalid/expired state | 400 {"error": "invalid_state"} |
| IdP returns error | Redirect: ?error=access_denied&error_description=... |
| Token exchange fails | Redirect: ?error=server_error |
| ID token validation fails | Redirect: ?error=server_error |
| User not in identity table | Redirect: ?error=access_denied&error_description=user_not_registered |
| DB errors | 500, log error |
Integration tests:
https://link.getorcha.com/oauth/callback/google to Google OAuth app redirect URIshttps://link.getorcha.com/oauth/callback/microsoft to Azure AD app redirect URIs