Connector OAuth
harn connect is the guided setup entry point for connector credentials. It is
intended for local operator setup: run the browser flow once, store tokens in
the workspace keyring namespace, then reference the resulting secret ids from
connector configuration.
Provider flows#
Built-in OAuth commands are available for Slack, Linear, and Notion:
harn connect slack \
--client-id "$SLACK_CLIENT_ID" \
--client-secret "$SLACK_CLIENT_SECRET" \
--scope "app_mentions:read chat:write"
harn connect linear \
--client-id "$LINEAR_CLIENT_ID" \
--client-secret "$LINEAR_CLIENT_SECRET"
harn connect notion \
--client-id "$NOTION_CLIENT_ID" \
--client-secret "$NOTION_CLIENT_SECRET"
The generic OAuth 2.1 path targets compliant protected resources:
harn connect generic acme https://mcp.example.com/mcp
harn connect --generic acme https://mcp.example.com/mcp
Connector packages can also declare their setup metadata in the registered
provider entry, letting operators run harn connect <provider> without
hardcoded provider logic in core:
[[providers]]
id = "acme"
connector = { harn = "acme-connector/lib" }
oauth = {
resource = "https://mcp.example.com/mcp",
authorization_endpoint = "https://auth.example.com/oauth/authorize",
token_endpoint = "https://auth.example.com/oauth/token",
registration_endpoint = "https://auth.example.com/oauth/register",
scopes = "mcp.read mcp.write",
token_endpoint_auth_method = "none",
}
Hosts use the same metadata for generic setup and repair UI. Every connector
package should declare a setup table on its [[providers]] entry:
[[providers]]
id = "acme"
connector = { harn = "acme-connector/lib" }
capabilities = ["webhook", "oauth"]
[providers.setup]
auth_type = "oauth2"
flow = "browser"
required_scopes = ["mcp.read", "mcp.write"]
setup_command = ["harn", "connect", "acme"]
validation_command = ["harn", "connect", "status", "--connector", "acme", "--json"]
[[providers.setup.configuration_environment]]
field = "oauth_client_id"
environment_names = ["ACME_OAUTH_CLIENT_ID"]
[[providers.setup.health_checks]]
id = "credentials"
kind = "command"
command = ["harn", "connect", "status", "--connector", "acme", "--json"]
[providers.setup.recovery]
missing_auth = "Run `harn connect acme`."
expired_credentials = "Refresh or reconnect the OAuth token."
revoked_credentials = "Revoke the stale local token, then reconnect."
missing_scopes = "Reconnect with the scopes listed in required_scopes."
inaccessible_resource = "Grant the connector access to the requested resource."
transient_provider_outage = "Retry after the provider or credential backend recovers."
harn connect setup-plan --connector <id> --json emits a host-renderable plan.
For browser OAuth, the plan includes a Harn-derived launch_command that does
not contain credentials. Native hosts execute that command and render the
typed NDJSON setup events. Connector-declared configuration_environment
entries allow only the named non-secret setup values; plans and status reports
contain the names but never the values.
harn connect status --connector <id> --json reports one of healthy,
missing_install, missing_auth, expired_credentials,
revoked_credentials, missing_scopes, inaccessible_resource, or
transient_provider_outage.
When endpoints are not supplied, Harn discovers protected-resource metadata,
then authorization-server metadata. For MCP login, the default client
registration mode is CIMD: Harn sends
https://harnlang.com/.well-known/oauth-client.json as its client id when the
authorization server advertises Client ID Metadata Document support. If CIMD is
not available and the server advertises dynamic client registration, Harn
registers a loopback client for the selected redirect URI.
GitHub App setup is separate from OAuth access tokens:
harn connect github \
--app-slug my-harn-app \
--app-id 12345 \
--private-key-file app.pem
The command opens the GitHub App installation URL and waits for the app setup
callback to include installation_id. You can skip the browser callback when
you already know the installation:
harn connect github \
--installation-id 67890 \
--app-id 12345 \
--private-key-file app.pem
Callback server#
OAuth and GitHub App setup callbacks use plaintext HTTP and bind only to
127.0.0.1 or localhost. The default redirect URI uses port 0, so Harn
chooses a random free local port and sends that concrete URI in the
authorization request; custom redirect URIs must also include an explicit port.
Callback listeners are single-use and time out after five minutes. If a callback
request includes an Origin header, Harn requires it to match the redirect
origin.
OAuth guarantees#
Harn always sends PKCE S256 parameters. The generic flow validates advertised
PKCE support when authorization-server metadata is available. The generic flow
also sends the resource parameter to both the authorization endpoint and token
endpoint; MCP server URLs are canonicalized first by lowercasing the scheme and
host, dropping default ports, and removing query strings, fragments, and trailing
slashes. Provider-specific flows let you override --resource when a provider
requires one.
Stored secrets#
OAuth setup stores connector-friendly ids in the same keyring namespace that
the nearest harn.toml uses:
<provider>/access-token<provider>/refresh-tokenwhen returned by the provider<provider>/oauth-tokenfor refresh metadata such as token endpoint, client id, client secret, scopes, resource, and expiration
GitHub App setup stores:
github/installation-<id>github/app-private-keywhen--private-key-fileis suppliedgithub/webhook-secretwhen a webhook secret is supplied
Use harn connect --list, harn connect --refresh <provider>, and
harn connect --revoke <provider> to inspect, refresh, or remove local
connector credentials.