# How is third-party authentication implemented?

> API layer & connectors — a good answer covers: OAuth2 flows, API keys, token refresh, credential storage and encryption, multi-tenant connected accounts.

Canonical page: https://llms-technical-reviews.com/connectors/q/auth/

## Verdict

[ACI](/p/aci/) has a full credential system. Each end user's account is a `LinkedAccount` keyed by your own `linked_account_owner_id`. The secret fields (API key, access and refresh tokens, client secret) are encrypted field by field with AWS KMS envelope encryption. Platform API keys are found by an HMAC-SHA256 lookup. OAuth2 runs through Authlib with PKCE. An expired token is refreshed when a function runs, and a rotated refresh token is saved. One weak point: the PKCE verifier travels in a signed but unencrypted `state` JWT that has no expiry.

[Klavis](/p/klavis/) has no credential store in the open code. Each MCP server reads a token from the `AUTH_DATA` env var or from a base64 `x-auth-data` header. The `-oauth` Docker images get that token from the hosted `api.klavis.ai`, so they need a Klavis API key. The Strata router can do standard MCP OAuth by itself, with a localhost:3030 callback, but it keeps the tokens as plaintext JSON in `.tokens/` and serves a single user.

Choose ACI if you need many end users with stored, refreshed credentials on your own infrastructure. Choose Klavis if one developer or one workload passes in tokens it already has, or if you are happy to let the Klavis cloud run OAuth.

More projects in this category are being researched.

## Per-project answers

### Klavis-AI/klavis (answered)

Third-party auth uses two approaches. **OAuth2 via Klavis cloud** (`_oauth_support/oauth_acquire.sh:1-93`): Docker wrapper calls api.klavis.ai to create OAuth instance, shows URL, polls until authorized. **Strata OAuth provider** (`open-strata/src/strata/mcp_proxy/auth_provider.py:160-200`): OAuthClientProvider with authorization_code/refresh_token grants. LocalTokenStorage (`auth_provider.py:27-54`) persists tokens to `.tokens/{name}/tokens.json`. CallbackServer on port 3030 (`auth_provider.py:110-154`). HTTPTransport (`transport/http.py:46-80`) passes provider when auth=oauth. **Multi-tenant**: per-server OAuth instances. Servers like Airtable (`mcp_servers/airtable/server.py:43-70`) extract tokens from AUTH_DATA or x-auth-data header.


Citations: [open-strata/src/strata/mcp_proxy/auth_provider.py:160-200](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L160-L200) · [open-strata/src/strata/mcp_proxy/auth_provider.py:27-54](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L27-L54) · [open-strata/src/strata/mcp_proxy/transport/http.py:46-80](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/transport/http.py#L46-L80) · [mcp_servers/airtable/server.py:43-70](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#L43-L70)

### aipotheosis-labs/aci (answered)

Third-party authentication uses three mechanisms. **PropelAuth** handles web portal auth (JWT tokens via HTTPBearer) — every user-facing route depends on `auth.require_user` (main.py:136, acl.py:15-19). **API keys** are for agent/programmatic access: validated by `validate_api_key` in dependencies.py (lines 48-69), which looks up the key via `crud.projects.get_api_key`, checks status (active/disabled/deleted), and returns a UUID key ID. API keys are stored encrypted at rest via the `Key` SQLAlchemy type decorator (custom_sql_types.py:29-48) which calls `encryption.encrypt()` using AWS KMS. A SHA-256 HMAC of the key is also stored for lookups. **Linked accounts** store end-user credentials per app per project (sql_models.py:398-455). The `security_credentials` JSONB column uses `EncryptedSecurityCredentials` (custom_sql_types.py:84-157) which encrypts individual fields (secret_key, access_token, refresh_token) via AWS KMS. The `OAuth2Manager` (oauth2_manager.py:16-162) handles the full OAuth2 code flow — `create_authorization_url` builds state-JWT-encoded redirect URLs, `fetch_token` exchanges codes with PKCE (S256), and `refresh_token` rotates expired tokens. Token expiry is checked in `security_credentials_manager.py:213-216` and auto-refreshed at execution time (lines 98-132). App-level OAuth2 client credentials can be overridden per configuration via `security_scheme_overrides` (sql_models.py:351-354). The multi-tenant model links each API key → agent → project → org (sql_models.py:69-127).


Citations: [backend/aci/server/dependencies.py:48-69](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/dependencies.py#L48-L69) · [backend/aci/server/oauth2_manager.py:16-162](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/oauth2_manager.py#L16-L162) · [backend/aci/common/db/custom_sql_types.py:84-157](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/custom_sql_types.py#L84-L157) · [backend/aci/common/encryption.py:1-55](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/encryption.py#L1-L55) · [backend/aci/server/security_credentials_manager.py:87-139](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/security_credentials_manager.py#L87-L139) · [backend/aci/common/db/sql_models.py:390-460](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/sql_models.py#L390-L460)
