Skip to main content

Connector Authentication

How self-hosted Align connects to Slack, Teams, Jira, GitHub, and other integrations.

Overview​

A self-hosted install talks directly to each platform's own OAuth endpoints. There is no Align-operated OAuth broker, and nothing in the authorization flow reaches Align. The authorize redirect goes from your users' browsers to Slack, Atlassian, Microsoft, GitHub, Linear or Zoom. The token exchange goes from your gateway to those same providers. Your gateway encrypts the tokens with your own master-encryption-key secret and stores them in your own database.

Every self-host uses its own OAuth apps. You create the app with each provider and give the gateway its client id and secret. Your users' consent screen shows your own app name, the registration lives in your own provider tenant, and your own administrator approves it. We do not hand a self-host Align's production app secret. One shared secret across customers cannot be revoked for one customer without breaking all of them, so we do not offer it.

You set a client id and secret for each OAuth-backed connector you enable. One with neither set is reported as not configured and the gateway refuses to begin its authorization flow, so there is no configuration in which the credentials stay empty and that connector still works.

You create each app yourself. Registering your own OAuth apps below lists what each one needs.

Two things on this page do not take a client id and secret. A GitHub App uses an app id and a private key instead, covered below. The align connector authorizes against nothing, because it presents your own tenant's Align credentials, so it is marked auth: none in the connector registry and skips the OAuth flow entirely.

How It Works​

Your infrastructure The provider
┌──────────────────────────────┐ ┌──────────────────────────┐
│ ┌─────────┐ ┌─────────┐ │ │ Slack, Atlassian, │
│ │ Browser │ │ Gateway │ │ │ Microsoft, GitHub, │
│ └────┬────┘ └────┬────┘ │ │ Linear, Zoom │
└───────┼──────────────┼───────┘ └────────────┬─────────────┘
│ │ │
│ 1. authorize redirect ──────────────────▶
│ │ │
◀───────────────── 2. consent screen ──────│
│ │ │
│ 3. redirect to your own callback │
├─────────────▶│ │
│ │ 4. token exchange ───────▶
│ ◀──── 5. tokens ────────────│
│ │ │
│ ▼ │
│ 6. encrypt, store in your database │

Step by step:

  1. A user clicks "Connect Slack" in your self-hosted Align UI.
  2. Your gateway redirects the browser to the provider's own authorize URL, for example https://slack.com/oauth/v2/authorize.
  3. The user grants permission on the provider's consent screen.
  4. The provider redirects the browser back to your instance at https://{your-api-domain}/oauth/callback/{connector}.
  5. Your gateway calls the provider's token endpoint directly to exchange the code for tokens.
  6. Your gateway encrypts the tokens and stores them in your database.

No step in this flow contacts Align infrastructure. If you firewall your install off from Align, connector authorization still works.

GitHub is the one exception to the shape of this flow, not to where it goes. If you configure a GitHub App by setting GITHUB_APP_ID, step 2 sends the user to that App's installation page rather than an authorize URL, and there is no authorization-code exchange: the callback carries an installation id, and the gateway mints short-lived installation tokens from GitHub afterwards. Both still talk only to github.com and api.github.com. Setting GITHUB_CLIENT_ID instead uses the ordinary OAuth flow above.

Registering Your Own OAuth Apps​

Create an app with the provider, then give the gateway its credentials.

The gateway reads each connector's credentials from a Kubernetes secret named with the chart's align.fullname followed by -oauth-<connector>. For an OAuth app the keys are client-id and client-secret. A GitHub App is the exception and uses app-id and app-private-key in the same -oauth-github secret.

ConnectorSecret name suffixGateway environment variablesApp to create
Slack-oauth-slackSLACK_CLIENT_ID, SLACK_CLIENT_SECRETSlack app
Jira and Confluence-oauth-atlassianATLASSIAN_CLIENT_ID, ATLASSIAN_CLIENT_SECRETOne Atlassian OAuth 2.0 (3LO) app, shared by both
GitHub-oauth-githubGITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET (keys client-id, client-secret), or GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY (keys app-id, app-private-key)GitHub OAuth app, or a GitHub App
Microsoft Teams-oauth-teamsTEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET (OAuth/Graph) and TEAMS_BOT_APP_ID, TEAMS_BOT_APP_PASSWORD, TEAMS_BOT_APP_TENANT_ID (Bot Framework, keys bot-app-id, bot-app-password, tenant-id)Microsoft Entra ID app registration, also used as an Azure Bot - see registering your own Azure Bot
Teams, SharePoint/OneDrive and Azure DevOps (one Entra app)-oauth-entraENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET, ENTRA_TENANT_ID (keys client-id, client-secret, tenant-id)One Microsoft Entra ID app registration in your tenant. Register three redirect URIs on it, once each: <api>/oauth/callback/teams, /azure-devops and /m365-documents. A per-connector secret (-oauth-teams, -oauth-azure-devops, -oauth-m365-documents) overrides the shared one. Admin consent is needed for ChannelMessage.Read.All and OnlineMeetingTranscript.Read.All; the other delegated permissions in the pack table need none. A Global Administrator or Privileged Role Administrator grants it once. A flow that has any of its own variables set uses only those and ignores the shared set. Put the shared app in -oauth-entra only: if you also set client-id in -oauth-teams, Teams ignores the shared tenant and uses common. One app means one consent, so every Graph token from it carries the union of the Teams and SharePoint permissions the admin approved. The Teams connector pod reads the shared set for its app-only Graph token (subscriptions and transcripts) by the same rule: if any key is set in -oauth-teams (client-id, client-secret, bot-app-id, bot-app-password or tenant-id), it uses only those. That token uses the .default scope, so it carries every application permission an admin consented to on the one app. For a narrower blast radius, give Teams its own app in -oauth-teams.
Notion-oauth-notionNOTION_PERSONAL_CLIENT_ID, NOTION_PERSONAL_CLIENT_SECRET (keys client-id, client-secret)A Notion public integration. Under Capabilities, tick "Read content". Add "Insert comments" only if you set connectors.notion.commentsGranted (see below). Set the redirect URI to <api>/oauth/callback/notion. Set connectors.notion.enabled: true, because the chart ships Notion off. The workspace connection and each person's CLI login both use this one integration. See below.
Zoom-oauth-zoomZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET (keys client-id, client-secret), plus ZOOM_WEBHOOK_SECRET (key webhook-secret) for real-time capture, and ZOOM_ROBOT_JID (key robot-jid) for Team Chat answersZoom OAuth app. Team Chat needs the app's Chatbot feature on, with its bot endpoint set to <api>/webhook/zoom/chat. Without webhook-secret every Zoom delivery is rejected. Without robot-jid the Team Chat route stays off.
Azure DevOps-oauth-azure-devopsAZURE_DEVOPS_CLIENT_ID, AZURE_DEVOPS_CLIENT_SECRET, AZURE_DEVOPS_TENANT_ID (keys client-id, client-secret, tenant-id), plus AZURE_DEVOPS_WEBHOOK_SECRET (key webhook-secret) for real-time captureMicrosoft Entra ID app registration with the Azure DevOps delegated permission. The tenant ID is required: there is no common fallback.
M365 Documents (SharePoint)-oauth-m365-documentsM365_DOCUMENTS_CLIENT_ID, M365_DOCUMENTS_CLIENT_SECRET, M365_DOCUMENTS_TENANT_ID (keys client-id, client-secret, tenant-id)A separate Microsoft Entra ID app registration with the Microsoft Graph delegated permission Sites.Read.All. The tenant ID is required: there is no common fallback.

Azure DevOps and M365 Documents are on by default (connectors.azureDevOps.enabled and connectors.m365Documents.enabled, both true). Until their secret exists the pod runs idle, the gateway env vars stay unset, and a user who clicks Connect gets an error instead of a Microsoft login. Set the flag to false to remove the pod and hide the card. Azure DevOps webhook capture rejects every delivery until webhook-secret is in its secret; M365 Documents has no webhook.

Linear, GitLab and Notion are wired through -oauth-linear, -oauth-gitlab and -oauth-notion and the matching LINEAR_PERSONAL_*, GITLAB_PERSONAL_* and NOTION_PERSONAL_* variables. The chart has no separate team-tier secret for those three.

Notion. Create the integration at notion.so/profile/integrations as a public integration. Tick only "Read content". Do not tick "Insert content", "Update content" or any user-information capability. An admin then connects Notion once from Connectors, and Notion's consent screen asks which pages to share. Align, and a Discover scan, read only those pages and the pages under them. To widen what a scan sees, share more pages with the integration in Notion and scan again. The Notion token does not expire and is encrypted with your master-encryption-key like every other connector token. There is no pasted-token path: the integration is the only way in.

Notion comments (off by default). Align posts a comment on a Notion page only when an agent asks it to through notion.post_comment. It does not reply to @Align mentions in Notion: there is no Notion webhook to hear them. The tool needs the "Insert comments" capability on your Notion integration. Notion's API does not report an integration's capabilities, so Align cannot check that against Notion. You record it yourself: tick "Insert comments" on the integration, then set connectors.notion.commentsGranted: true. Until you do, the tool posts nothing and says why. If you set the flag without ticking the capability, Notion refuses the comment and the call fails with Notion's status.

Linear's team connection reuses the Linear app: at authorize time it asks for read and, once an admin switches the ticket feature on, issues:create (and comments:create for the comment feature), and authorizes as the app (actor=app), so @Align create ticket can create an issue that shows as created by Align, while the personal CLI tier asks for read alone.

Read-only first consent (ALI-1800). For Slack, Jira and Linear the first consent asks for read scopes only, and a write scope is requested when its feature is switched on. Your own OAuth app must still list every scope Align can ask for, read and write, because a provider only grants scopes the app has registered. Slack, Atlassian and Linear take the scope list from each authorisation request. The per-connector read and write lists are in the integration pages.

align.fullname is your release name when it already contains align, otherwise your release name with -align appended, and fullnameOverride replaces it entirely. A release named acme therefore reads acme-align-oauth-slack. Check what your install renders before creating the secrets:

helm template <release> oci://$ALIGN_REGISTRY/align/charts/align \
-f your-values.yaml | grep -oE '[a-z0-9-]+-oauth-[a-z-]+' | sort -u

That reads the names the gateway is wired to look for, so it works whether or not the chart creates the secrets itself.

Supplying the credentials​

Production installs keep secrets.create: false, which is the chart default, and pre-create those secrets with your own secret manager. See Secrets Management for the External Secrets, Sealed Secrets, SOPS and Vault paths. The names and keys in the table above are what the gateway looks for, whichever mechanism creates them.

For development only, secrets.create: true lets the chart build them from values:

secrets:
create: true # development and testing only - never commit real credentials
oauth:
slack:
clientId: "your-slack-client-id"
clientSecret: "your-slack-client-secret"
atlassian:
clientId: "your-atlassian-client-id"
clientSecret: "your-atlassian-client-secret"

The chart has no secrets.oauth.zoom value, so Zoom's secret has to be created directly even in development.

Redirect URIs​

You do not set redirect URIs per connector. The chart derives each one from gateway.apiBaseUrl:

{gateway.apiBaseUrl}/oauth/callback/{connector}

gateway.apiBaseUrl already includes the scheme, so nothing is prefixed to it. With gateway.apiBaseUrl: https://api.align.example.com, the Slack callback is https://api.align.example.com/oauth/callback/slack. Register that exact URL in your provider app. If gateway.apiBaseUrl is unset, the chart builds the same URL from the first host in gateway.ingress.hosts.

Scopes​

Align requests a fixed scope set per connector. That set is defined in the release. No Helm value, environment variable or per-tenant setting changes it, so registering your own app does not narrow what Align asks for.

What your own app does give you is the registration itself, the name your users see on the consent screen, and your own administrator's approval of the permission set before any user can grant it. Your app has to permit at least the scopes Align requests, or the provider rejects the authorization.

A GitHub App is the exception, because a GitHub App has no OAuth scopes. Its access comes from the repository and organization permissions set on the App itself, and Align's scope list is ignored on that path. Align reads repository content and metadata and reads and writes issues and pull requests, so configure the App's permissions to match what your review allows.

The per-connector scope lists are on the integration pages, including Jira, Confluence, Slack and Zoom. If a scope in that set blocks your review, tell us which one. Narrowing it is a change to the product, not something you can configure.

Connecting Integrations​

From the UI​

  1. Navigate to Settings → Connectors
  2. Click the connector you want to enable
  3. Follow the OAuth authorization flow
  4. Grant permissions when prompted
  5. The connector is now active

Required Permissions​

A new connection for Slack, Jira and Linear asks for read scopes first. A write scope is asked for later, when its feature is switched on (ALI-1800). The exact lists are on the integration pages and are read from services/gateway/src/auth/connectorScopeSets.ts. A tenant that connected before that change keeps the wider grant it already holds.

  • Slack: reads channels, private channels and direct messages Align is added to, and user profiles. Posting messages, direct messages, the /align command, joining channels and reactions are separate write features.
  • Jira and Confluence: read issues, projects, users and Confluence pages and comments. Creating tickets and comments, and Confluence comments, are separate write features.
  • Microsoft Teams: read-only delegated permissions, including ChannelMessage.Read.All and OnlineMeetingTranscript.Read.All, which need admin consent.
  • GitHub: set by the App's own permissions, not by a scope list.
  • Linear: read. Creating issues (issues:create) and posting comments (comments:create) are separate write features.

Network Requirements​

Your self-hosted Align needs outbound HTTPS access to:

EndpointPurpose
slack.comSlack authorization and token exchange
api.slack.comSlack API calls
auth.atlassian.comJira and Confluence authorization and token exchange
api.atlassian.comJira and Confluence API calls
github.comGitHub authorization and token exchange
api.github.comGitHub API calls
login.microsoftonline.comTeams authorization and token exchange
graph.microsoft.comTeams API calls
linear.appLinear authorization
api.linear.appLinear token exchange and API calls
zoom.usZoom authorization and token exchange
api.zoom.usZoom API calls
gitlab.comGitLab authorization, token exchange and API calls (personal CLI tier)
api.notion.comNotion authorization, token exchange and API calls

No Align-operated host appears in this list. The authorization flow does not contact Align.

Inbound webhooks require your instance to be accessible from the internet (or configure a webhook relay).

Webhook Configuration​

Each provider sends webhooks to the gateway, at the address in gateway.apiBaseUrl. Use these paths when you set a webhook up in the provider:

IntegrationWebhook endpoint
Slack{gateway.apiBaseUrl}/webhook/slack/events
Teams{gateway.apiBaseUrl}/webhook/teams
Jira{gateway.apiBaseUrl}/webhook/jira
Confluence{gateway.apiBaseUrl}/webhook/confluence
GitHub{gateway.apiBaseUrl}/webhook/github
Linear{gateway.apiBaseUrl}/webhook/linear
Zoom{gateway.apiBaseUrl}/webhook/zoom

Firewall Configuration​

Allow inbound HTTPS (443) from these IP ranges:

Token Security​

  • OAuth tokens are encrypted at rest using your master-encryption-key secret
  • Tokens are never sent to Align. The exchange runs between your gateway and the provider
  • Token refresh happens directly between your instance and the provider
  • You can revoke access at any time from the provider's settings

Troubleshooting​

"OAuth authorization failed"​

  1. Check your instance has outbound access to the provider's authorization host, listed under Network Requirements
  2. Check the callback URL registered in the provider app matches {gateway.apiBaseUrl}/oauth/callback/{connector} exactly
  3. Check browser console for CORS or redirect errors

"Webhook verification failed"​

  1. Verify your API endpoint is publicly accessible
  2. Check TLS certificate is valid (not self-signed)
  3. Confirm firewall allows inbound from the provider

"Token refresh failed"​

  1. Check outbound access to the provider's API
  2. User may need to reauthorize if permissions changed
  3. Check provider's status page for outages