Connect an external app to Wallow
Use this guide when your app lives in another repository and runs on its own domain. You need a running Wallow deployment and a Node 24 or newer app server. Your app owns its UI, server, container, and session store. Wallow supplies identity and platform APIs.
The browser calls your app's BFF on the app's own origin. The BFF keeps tokens on the server and calls Wallow with those tokens. Login redirects the browser to Wallow and then back to your app. You do not share cookies between the two domains.
1. Identify the Wallow deployment
Ask the platform operator for the public OIDC issuer and the API base URL. Use the
issuer advertised by the deployment's discovery document, including any path prefix.
Do not infer the issuer from the login UI hostname: the auth UI and the OIDC endpoints
can have different URLs. For example, a deployment can serve its API and issuer under
https://platform.example.com/api and its login UI under /auth.
Confirm that setup is complete and that you can sign in to Wallow's management UI. For a local Wallow checkout, follow the developer guide to start the API and frontends first.
Keep these addresses separate:
OIDC_ISSUER: the public issuer used for browser redirects and token identity.BFF_API_BASE_URL: the Wallow API base reachable from your app server. SDK requests such as/v1/identity/users/meare appended to this base.- Your app origin, such as
https://app.example.com: the browser's destination after login.
If the BFF needs internal discovery, set OIDC_METADATA_URL to the full discovery
URL, including /.well-known/openid-configuration. This changes where discovery is
fetched. Token, userinfo, and JWKS URLs advertised there must also be reachable from
the BFF. The SDK pins authorization and end-session redirects to the public issuer.
It does not rewrite every backend URL to BFF_API_BASE_URL.
2. Create an organization and register an application
Sign in to Wallow, create an organization, and open its clients screen. Register an application, which is a confidential OIDC client bound to that organization. Use your app's name and register these URLs on your app's domain:
| Field | Example |
|---|---|
| Redirect URI | https://app.example.com/bff/callback |
| Post-logout redirect URI | https://app.example.com/ |
| Back-channel logout URI | https://app.example.com/bff/backchannel-logout |
Supply the post-logout URI even though the registration form permits leaving it blank; the SDK requires it. Organization registration exposes back-channel logout, not a front-channel logout field.
Use exact URLs, including paths and trailing slashes. The identity server must be able to reach the back-channel logout URL. Use a separate registration for local development, with your local origin and port.
Select the API scopes the app needs. Include openid profile email offline_access
for user sign-in and refresh, plus the allowed API scopes for your operations.
For the example's current-user request, include users.read.
Copy the one-time credential reveal and its environment block before leaving the page. The organization binding comes from registration; your app does not choose another organization by changing an environment variable. Users must satisfy that organization's membership and enrollment policy. New organizations default to invite-only. Invite your users or configure the intended enrollment policy before testing with a new user. Application credentials do not grant a user membership.
3. Install a matching SDK
Use the SDK installation instructions to configure
GitHub Packages access. Install @bc-solutions-coder/sdk,
@bc-solutions-coder/api-errors, and the SDK's supported Redis peer, redis@^4.7.0.
Pin the Wallow package versions and commit your app's lockfile.
Before choosing versions, check the selected package's release and successful
package-publish.yml run. A merge to Wallow's main branch, a platform Docker release,
and a successful SDK publication are separate events. The version in a checkout's
package.json does not prove that its current source has been published.
The SDK depends on api-errors; that dependency must be available too. Maintainers
must resolve generated-contract drift, merge the relevant release changes, and verify
package publication before telling consumers to install a new version. See
package release stages.
Choose packages by capability
| Package or entry | What your app gets | Additional dependency |
|---|---|---|
@bc-solutions-coder/sdk |
Typed platform operations, browser SDK, login and logout helpers | None |
@bc-solutions-coder/sdk/server |
BFF routes, sessions, refresh, and authenticated API proxy | redis@^4.7.0 for production sessions |
@bc-solutions-coder/sdk/server/service |
Service-account API client | Redis when using a shared token cache |
@bc-solutions-coder/sdk/server/forwarded |
Trusted-proxy address and origin helpers | None |
@bc-solutions-coder/sdk/server/passthrough |
Session-less reverse proxy | None; this is not the external app BFF |
@bc-solutions-coder/sdk/query |
Generated TanStack Query operations | Compatible @tanstack/react-query and its React peers |
@bc-solutions-coder/api-errors |
Failure parsing, error codes, and readable messages | None |
SDK subpaths are entry points in one package, not separate packages to install.
The SDK declares its API-errors dependency. Install API-errors directly as well when
your app imports it. Both packages must be available to the installing identity.
Use the selected SDK version's peerDependencies to choose optional library versions.
Your framework, React, and styling remain your app's dependencies.
Grant package access to the consuming repository
For local installs, use a classic personal access token with read:packages and an
account that can read both packages. A Wallow application secret does not authenticate
to GitHub Packages. See GitHub's npm registry instructions.
For GitHub Actions, grant the consuming repository read access in Manage Actions
access on each package's settings page. Give its workflow packages: read and
contents: read; authenticate installation with its GITHUB_TOKEN. Repository access
to Wallow's source alone does not establish cross-repository package access. If that
token cannot access the packages, use a repository secret containing an authorized
classic read token. See GitHub package access permissions.
Configure the registry with actions/setup-node using
registry-url: https://npm.pkg.github.com and scope: '@bc-solutions-coder'.
Set NODE_AUTH_TOKEN for the install step. For Docker builds, forward the read token
as the build secret described in the SDK guide, not as a runtime application secret.
Verify access using the same identity that builds the app:
npm view @bc-solutions-coder/sdk version dependencies peerDependencies --json
npm view @bc-solutions-coder/api-errors version --json
A 401 or 403 is an authentication or permission failure. A 404 can mean the package or version has not been published, or the caller cannot read it. Check publication and permissions for both packages before changing imports or SDK code.
Wallow also provides a check that installs into a temporary directory outside the workspace, loads every public entry point, runs a BFF and API smoke check, and checks the consumer's types. From the Wallow root, run:
pnpm check:external-consumer
Without arguments, it checks freshly packed local code without a GitHub package token. To check published versions with your configured registry credentials, pass the SDK version and API-errors version in that order:
pnpm check:external-consumer "$SDK_VERSION" "$API_ERRORS_VERSION"
The check uses Node 24+, Wallow's installed pnpm toolchain, and access to npmjs.org for third-party dependencies. It does not log in to a Wallow deployment or prove that a different repository's CI token has access; run the versioned check with that identity as well. The publishing workflow checks that the API-errors version exists before publishing an SDK that depends on it. If the dependency check fails, finish publishing API-errors first, then rerun the SDK publication.
Use a local Wallow checkout before publication
To validate the source in a sibling folder, build and pack the two public packages. Run these commands from the Wallow root:
pnpm install --frozen-lockfile
pnpm exec turbo run build --filter=@bc-solutions-coder/sdk...
mkdir -p /tmp/wallow-packages
pnpm --dir packages/api-errors pack --pack-destination /tmp/wallow-packages
pnpm --dir packages/sdk pack --pack-destination /tmp/wallow-packages
In your app repository, install the two emitted .tgz files by their exact paths,
along with redis@^4.7.0, using your package manager. Install both archives in the
same operation. Use pnpm pack, which rewrites Wallow's workspace dependency protocols
and applies its published exports. Import the public package names as usual.
This validates a local build, not a published release. Replace temporary archive paths
with pinned registry versions before handing your app's build to CI, or supply the
archives as explicit build inputs. Do not import packages/sdk/src from the sibling
checkout or copy its workspace: dependencies into your app.
4. Mount the BFF and connect your UI
Follow mounting the BFF to create a
server-only, lazily initialized createWallowBffServer() instance and mount both
/bff/* and /api/*. Preserve the incoming request, including the runtime's peer
address, when passing it to the SDK. Keep server imports out of your browser bundle.
Use createWallowSdk({ baseUrl: "/api" }) for browser API calls. Use
loginRedirect("/").href for full-document sign-in links, getCurrentUser({ client: sdk.client }) to read
the session, and
logout() for sign-out. Pass the instance's client to generated API operations.
For SSR, create a separate SDK instance for every incoming request and forward that
request's cookies. Never share an authenticated SDK instance across users.
The reference implementation is apps/minimal-app in the Wallow checkout:
File under apps/minimal-app/ |
What to adapt |
|---|---|
src/lib/bff.server.ts |
BFF instance and health handler |
src/routes/bff/$.ts and src/routes/api/$.ts |
TanStack Start route adapters |
src/start.ts and src/router.tsx |
Request-scoped SSR and browser SDK instances |
src/routes/index.tsx |
Sign-in, session, typed request, and sign-out |
src/lib/service-client.server.ts |
Optional service-account request |
Use your app's framework configuration and styling. The example's Vite config imports
the private @bc-solutions-coder/config package; it is not an external-app starter you
can copy unchanged. Wallow's auth, ui, styles, query, env, and testing
workspace packages are also private. External apps use the public SDK and API-errors
packages with their own framework and UI dependencies.
Keep CSRF enabled. The SDK handles the token for its browser-to-BFF mutations and logout. Custom server routes and server functions still need their own authorization, input validation, and CSRF protection where they use browser cookies. A service-account helper does not secure the public route that calls it.
5. Configure the container at runtime
Start with the registration reveal. For an app on https://app.example.com, the runtime
environment has this shape. Replace placeholders with your deployment's actual values:
OIDC_ISSUER=https://platform.example.com/api
OIDC_CLIENT_ID=<application client id>
OIDC_CLIENT_SECRET=<application client secret>
OIDC_REDIRECT_URI=https://app.example.com/bff/callback
OIDC_POST_LOGOUT_REDIRECT_URI=https://app.example.com/
OIDC_SCOPES=openid profile email offline_access users.read
BFF_API_BASE_URL=https://platform.example.com/api
COOKIE_PASSWORD=<random value of at least 32 characters>
REDIS_URL=redis://<session-store-host>:6379
Use Valkey or Redis for production sessions. Make it reachable from every app replica.
Keep the cookie password stable across restarts and consistent across replicas; see
cookie password rotation.
Keep SESSION_TTL_SECONDS at or below the issued refresh-token lifetime.
Supply these values through your deployment's runtime environment or secret store.
Do not expose them through VITE_* variables or embed them during the frontend build.
For plain HTTP local development only, set COOKIE_SECURE=false.
The package-registry read token is a separate build-time credential. Use the Docker build-secret recipe for installation. The running app needs its OIDC and session secrets, not a GitHub package token.
Your image must contain your app's built server and runtime dependencies. The
apps/minimal-app/Dockerfile builds from the entire Wallow workspace; adapt your own
Dockerfile to install the published packages instead of copying Wallow's private workspace.
Route your app domain over HTTPS to that container. Forward /bff/* and /api/* to
its server, alongside the UI. Configure trusted proxy addresses when your runtime sits
behind ingress; see the SDK environment reference.
For SSR self-fetches, configure internalOrigin for the app's own listener, not the
Wallow backend. The example uses its local server port.
6. Add a service account only for app-owned operations
For work without a signed-in user, such as accepting a contact inquiry, register a separate service account in the same organization. Grant only the needed scopes. Add its revealed credentials to the app server's runtime environment:
OIDC_SERVICE_CLIENT_ID=<service account client id>
OIDC_SERVICE_CLIENT_SECRET=<service account secret>
OIDC_SERVICE_SCOPES=inquiries.write
Use createServiceClient() from @bc-solutions-coder/sdk/server/service and pass its
client to generated operations. It shares OIDC_ISSUER, BFF_API_BASE_URL, and the
optional discovery and Redis configuration. See the
service client reference.
Do not use this identity for requests that need the signed-in user's permissions.
7. Verify the deployed integration
Run these checks against your external app and the intended Wallow deployment:
- Open the app while signed out. Its session endpoint,
/bff/user, returns401. - Follow sign-in. Confirm that the browser reaches the public issuer and returns to the exact registered callback. Complete consent and any membership steps.
- Confirm that
/bff/usernow returns the session and a typed current-user API call succeeds through the app's/apiroute. Browser API traffic stays on the app origin. - Perform an authorized mutation. Confirm that SDK CSRF handling succeeds, and that the same cookie-authenticated mutation without its CSRF header is rejected.
- Sign out using the SDK helper. Confirm that the app session becomes unauthorized.
- Sign in again, end the Wallow session elsewhere, and confirm that back-channel logout invalidates the app's server-side session without relying on an iframe.
- If configured, submit an anonymous inquiry and verify that it reaches the correct organization through the service account.
- Restart the app and test through each replica with the shared session store.
A successful build or configuration health response alone does not prove discovery, credentials, Redis connectivity, membership, or logout delivery. Complete the login and API checks before treating an app as integrated.
For an agent working in another repo, provide this guide, the Wallow checkout path or revision, the target issuer and API URL, and the selected SDK version. Keep secret values in the deployment environment. Ask the agent to adapt the listed example files and report the results of these checks.