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 the sign-in app
Section titled “Register the sign-in app”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).
| Setting | Value |
|---|---|
| Redirect URI | https://<your gateway>/auth/callback |
| Grant types | Authorization code and refresh token |
| Scopes | openid email profile, plus offline_access when your directory offers it |
| Client authentication | Client 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.
Who gets in
Section titled “Who gets in”Three settings decide who gets in. Each is optional. They apply in this order:
- 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.
admin_groupsinportward.toml(on AWS, the Admin groups parameter): members manage Portward (connections, what the AI may see).user_groups(andusers; 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 theusersline, everyone your directory lets in can ask, whateveruser_groupssays.
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.
Microsoft Entra ID
Section titled “Microsoft Entra ID”Redirect URI: https://<your gateway>/auth/callback (platform Web). Scopes the gateway asks for:
openid email profile offline_access.
In the Entra admin center:
- App registrations → New registration. Name: Portward. Supported account types: this organizational directory
only. Redirect URI: Web,
https://<your gateway>/auth/callback. - 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. - Certificates & secrets → New client secret. Copy its Value into
.env. Note the expiry date: renew it before then, or sign-in stops. - 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.
- 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_groupsanduser_groups). - Optional: API permissions → Add a permission → Microsoft Graph → Delegated permissions, add
openid,email,profileandoffline_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:
- 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. - 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, nothttps://<your org>.okta.com/oauth2/default. The group claim in step 3 is set on the org server. On your own server,init1.1.0 refuses the org server’s address. Step 2 of the server install shows how to set it inportward.tomlinstead. - 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 examplePortward), 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 namegroups.) - Ask for the
groupsscope, because Okta only sends groups when asked for them. On AWS, putgroupsin Extra scopes. On Google Cloud, setdirectory_scopes = ["groups"]. On your own server or Azure, addscopes = ["groups"]under[auth]inportward.tomland restart the gateway.
Google Workspace
Section titled “Google Workspace”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:
- APIs & Services → OAuth consent screen: User type Internal (only your Workspace accounts can sign in).
- Credentials → Create credentials → OAuth client ID → Web application. Authorized redirect URI:
https://<your gateway>/auth/callback. Copy the client ID and secret. - 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.
Any other OpenID Connect directory
Section titled “Any other OpenID Connect directory”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.
WorkOS, for teams without a directory
Section titled “WorkOS, for teams without a directory”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:
| Setting | Value |
|---|---|
| Client ID | https://<your gateway>/setup/confirm/client.json, a document the gateway serves |
| Redirect URI | https://<your gateway>/setup/confirm/callback |
| Scope | openid |
In the WorkOS dashboard:
- 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. - Connect → Configuration: turn on Client ID Metadata Document and Dynamic Client Registration.
- Connect → Resource Indicators: add both connector addresses, exactly:
https://<your gateway>/mcpandhttps://<your gateway>/admin/mcp. - Copy your AuthKit domain (for example
https://your-team.authkit.app). That is the issuer. - To let people in by email domain (
users = ["@yourcompany.com"]), addemailandemail_verifiedto the access token’s JWT template. Otherwise list people by their sign-in id.
How it works
Section titled “How it works”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.