Skip to content

Sign-in setup

People sign in to Portward with the account they already use at work. The gateway signs them in through your company directory itself, with OpenID Connect, the way self-hosted tools such as GitLab or Grafana do. Portward never learns who signs in, or how many. OpenID Connect is the one sign-in standard the gateway supports. Microsoft Entra ID, Okta and Google Workspace are the directories we test against. Other directories that support OpenID Connect should work if they meet the settings under Any other OpenID Connect directory. We don’t test them. A team without a directory can use WorkOS instead.

Register one app for the gateway in your directory, once. It is a web app with a client secret. The gateway signs people in through it with the standard flow for web apps (authorization code with PKCE).

SettingValue
Redirect URIhttps://<your gateway>/auth/callback
Grant typesAuthorization code and refresh token
Scopesopenid email profile, plus offline_access when your directory offers it
Client authenticationClient secret, sent in the body of the token request

<your gateway> is your gateway’s host name: <name>.gw.portward.ai through the Portward relay (the key page shows it), or a name of your own such as portward.yourcompany.com. The redirect URI must match exactly, with https and no slash at the end.

The gateway reads your directory’s settings from <issuer>/.well-known/openid-configuration. When they list offline_access, as Entra’s and Okta’s do, it asks for openid email profile offline_access. Google’s don’t, so with Google it asks for openid email profile and adds access_type=offline. With scopes = ["groups"] under [auth] (Okta), it asks for openid email profile groups offline_access. When an admin confirms a change that gives more access, the gateway sends them through the same app, with the same redirect URI and scopes, and makes them sign in again.

You’ll need three things from the app:

Where it goes on your own server
The issuer: your directory’s sign-in address--directory in the install’s init command
The app’s client id--client-id in the same command
The app’s client secret.env, as PORTWARD_DIRECTORY_SECRET

On AWS, Azure and Google Cloud, the deploy form asks for the same three. On your own server, create the install files with them: step 2 of the server install has the init command, which runs from the gateway’s image with docker run. Through the Portward relay it needs no address; with an address of your own, add --domain portward.acme.com.

Then put the secret in .env, run docker compose up -d, and run doctor. Its directory line checks that your directory answers and that its issuer matches your settings. Its directory app line checks that the secret is in place (it can’t check that the secret is right) and shows the redirect address to register.

Three settings decide who gets in. Each is optional. They apply in this order:

  1. Your directory decides who may sign in to the app at all. Assign the app to groups, as each directory’s section below shows. Disable people here when they leave. The gateway asks your directory again at least every hour, so someone you disable loses access within the hour.
  2. admin_groups in portward.toml (on AWS, the Admin groups parameter): members manage Portward (connections, what the AI may see).
  3. user_groups (and users; on AWS, User groups and Other users): members may ask questions of the data. If both are empty, only admins can ask. If you remove the users line, everyone your directory lets in can ask, whatever user_groups says.

Groups are written as your directory sends them: Microsoft Entra sends group ids (copy the “Object ID” of the group), Okta and others send group names.

The first time someone uses Portward from Claude or ChatGPT, the gateway asks them once to allow that app. That stops someone from sending a link that connects your account to their app without you knowing.

Redirect URI: https://<your gateway>/auth/callback (platform Web). Scopes the gateway asks for: openid email profile offline_access.

In the Entra admin center:

  1. App registrations → New registration. Name: Portward. Supported account types: this organizational directory only. Redirect URI: Web, https://<your gateway>/auth/callback.
  2. On the app’s page, copy the Application (client) ID and the Directory (tenant) ID. Your issuer is https://login.microsoftonline.com/<tenant id>/v2.0.
  3. Certificates & secrets → New client secret. Copy its Value into .env. Note the expiry date: renew it before then, or sign-in stops.
  4. Token configuration → Add groups claim. Choose Groups assigned to the application, shown in the ID token as Group ID. With any other choice, people in more than 200 groups get no groups at all.
  5. Enterprise applications → Portward → Properties: Assignment required = Yes. Then Users and groups: add the groups that should use Portward (the same groups you list in admin_groups and user_groups).
  6. Optional: API permissions → Add a permission → Microsoft Graph → Delegated permissions, add openid, email, profile and offline_access, then Grant admin consent, so people aren’t asked to consent to the sign-in itself.

Entra doesn’t mark the email addresses in its tokens as verified, so use groups (not "@yourcompany.com") with Entra.

Redirect URI: https://<your gateway>/auth/callback. Scopes the gateway asks for, once you add groups (step 4): openid email profile groups offline_access.

In the Okta admin console:

  1. Applications → Create App Integration → OIDC, Web Application. App integration name: Portward. Grant types: Authorization Code and Refresh Token. Sign-in redirect URI: https://<your gateway>/auth/callback. Leave the sign-out redirect URI empty. Require PKCE as additional verification can stay as Okta sets it, because the gateway always sends PKCE. Assignments: the groups that should use Portward, both the admins group and the groups whose members ask questions. For a first test, the admins group alone is enough.
  2. Copy the Client ID and Client secret. Leave Client authentication as Client secret. Okta then takes the secret in the body of the token request, the way the gateway sends it. Your issuer is https://<your org>.okta.com, Okta’s org server, not https://<your org>.okta.com/oauth2/default. The group claim in step 3 is set on the org server. On your own server, init 1.1.0 refuses the org server’s address. Step 2 of the server install shows how to set it in portward.toml instead.
  3. Sign On → Token claims → Show legacy configuration → Group Claims → Edit: in Groups claim filter, name the claim groups, then choose Starts with and a prefix your Portward groups share (for example Portward), or Matches regex and .*, the simplest for a first test. The filter must let through every group you name in the gateway’s admin and user groups, because Okta sends only the groups that pass it. Make sure you are a member of the admins group yourself, since you sign in as the first admin. (The newer Add expression form won’t take the name groups.)
  4. Ask for the groups scope, because Okta only sends groups when asked for them. On AWS, put groups in Extra scopes. On Google Cloud, set directory_scopes = ["groups"]. On your own server or Azure, add scopes = ["groups"] under [auth] in portward.toml and restart the gateway.

Redirect URI: https://<your gateway>/auth/callback. Scopes the gateway asks for: openid email profile, with access_type=offline in place of offline_access, which Google doesn’t offer.

In the Google Cloud console, in a project that belongs to your Workspace:

  1. APIs & Services → OAuth consent screen: User type Internal (only your Workspace accounts can sign in).
  2. Credentials → Create credentials → OAuth client ID → Web application. Authorized redirect URI: https://<your gateway>/auth/callback. Copy the client ID and secret.
  3. Your issuer is https://accounts.google.com.

Google’s sign-in carries no groups. Use users = ["@yourcompany.com"], since Google verifies Workspace email addresses. Name admins in admin_subjects. To find your sign-in id, sign in through the admin connector once. The gateway’s log shows it.

Redirect URI: https://<your gateway>/auth/callback. Scopes the gateway asks for: openid email profile offline_access when the directory lists offline_access, otherwise openid email profile with access_type=offline.

For Keycloak, Ping, JumpCloud, OneLogin, Auth0 and similar directories, register a confidential web app with that redirect URI and the authorization code and refresh token grants. Let it take the client secret in the body of the token request. Copy the issuer exactly as the directory’s /.well-known/openid-configuration shows it. The gateway refuses a directory whose issuer doesn’t match. If groups come in a claim other than groups, add groups_claim = "<claim>" under [auth] in portward.toml (your own server or Azure). The AWS and Google Cloud templates read groups only from groups. If groups come only with an extra scope, add it as step 4 of Okta shows: Extra scopes on AWS, directory_scopes on Google Cloud, scopes under [auth] elsewhere.

With WorkOS AuthKit, you register no app for the gateway. Claude and ChatGPT send people to WorkOS to sign in. Claude and ChatGPT register themselves at WorkOS, so you register no app there for them. Each asks WorkOS for access to one connector address. This works on your own server: in step 2 of the server install, give init --issuer https://your-team.authkit.app in place of --directory, --client-id and --admin-group. The AWS, Azure and Google Cloud templates need a directory.

The gateway starts a sign-in itself only when an admin confirms a change that gives more access. For that sign-in it uses:

SettingValue
Client IDhttps://<your gateway>/setup/confirm/client.json, a document the gateway serves
Redirect URIhttps://<your gateway>/setup/confirm/callback
Scopeopenid

In the WorkOS dashboard:

  1. Authentication: turn on the sign-in methods your team uses (email code, Google, Microsoft, SSO). AuthKit lets anyone create an account unless you turn sign-up off or use SSO only. The gateway lets in only the people you list in users, so a stranger who signs up gets an account and still sees none of your data.
  2. Connect → Configuration: turn on Client ID Metadata Document and Dynamic Client Registration.
  3. Connect → Resource Indicators: add both connector addresses, exactly: https://<your gateway>/mcp and https://<your gateway>/admin/mcp.
  4. Copy your AuthKit domain (for example https://your-team.authkit.app). That is the issuer.
  5. To let people in by email domain (users = ["@yourcompany.com"]), add email and email_verified to the access token’s JWT template. Otherwise list people by their sign-in id.

Claude and ChatGPT treat the gateway as their sign-in server (they register themselves with it automatically). The gateway sends the person to your directory and checks what comes back: the signature, issuer, audience and the one-time values it sent. It asks the person once per app to allow it. Then it gives the app access for one hour to one connector, data or admin. Every renewal asks your directory again. Changes that give more access (exposing a dataset, showing a column) make the admin sign in again at the directory, or at WorkOS, before they’re saved.