Shyft SolutionsDev guideExternal apps & links
GitHub

Not every tile on the portal is a first-party app we proxy and mint a JWT for. Some apps speak OIDC and authenticate directly against our Authentik; others are just links to third-party SaaS we don't run at all. This guide covers those two cases — when to use each, and how to register them. For the proxied first-party flow, see Register an app.

The three tile types

When you register an app at /admin/apps/new, the first choice is Type. It decides whether the portal proxies the app, mints a token for it, and polls its health — or just shows a tile that launches it.

TypeWhat it isPortal proxies it?Who enforces access
Internal app (Shyft auth)First-party app that verifies the portal's JWT. Served under /apps/<slug>/.Yes — JWT minted + proxied.Portal (allowed groups + role mapping).
Authentik app (OIDC direct)We host it, but it does its own OIDC dance against our Authentik. Tile links out.No.Authentik (a policy binding on the application). See below.
Third-party linkA plain bookmark to external SaaS (GitHub, Atlassian, Miro, AWS SSO…). The portal does no auth.No.The destination (its own login).

Both external types skip the proxy entirely: the tile is a real external link (target="_blank"), there's no /apps/<slug>/ route, no role mapping, and no health polling. The rest of this guide is the two of them.

Rule of thumb

If you wrote the app and it imports shyft-auth, it's Internal. If it's off-the-shelf software you deployed that supports OIDC, it's Authentik (OIDC direct). If it's a SaaS you just want a launcher for, it's a Third-party link.

The simplest case — a bookmark. No Authentik setup, no app config, nothing to deploy. You're just putting a recognizable tile on the home grid.

  1. /admin/apps/newType: Third-party link.
  2. Display name, Link URL (any absolute URL), and an Icon URL (the brand logo — tiles render it; otherwise they fall back to the name's initials).
  3. Visible to: Everyone for org-wide tools, or Specific groups to scope it.
  4. Save. The tile opens the URL in a new tab.
GitHub        https://github.com/ShyftSolutions
Atlassian     https://shyft.atlassian.net
Miro          https://miro.com/app/
AWS SSO       https://shyft.awsapps.com/start
A link tile makes no promises about access

The portal doesn't authenticate third-party links — it just shows the tile and opens the URL. Whether the user can actually get in is entirely up to the destination (Google SSO, the vendor's own login, etc.). Hiding the tile from a group hides the bookmark, not the service.

Authentik OIDC apps

These are apps we host that support OIDC and authenticate against our Authentik directly (so the portal never sees their traffic). The order matters: set it up in Authentik first, because the Authentik application's slug becomes part of the issuer URL the app needs.

Step 1 — create the provider + application in Authentik

In the Authentik admin:

  • OAuth2/OpenID provider:
    • Client type confidential (you'll copy the client ID + secret into the app's config).
    • Redirect URI = the app's own callback, matching_mode: strict. The path is whatever the app uses — for ExcaliDash it's fixed at <app-origin>/api/auth/oidc/callback.
    • An RS256 signing key; leave issuer mode = per provider (issuer becomes …/application/o/<slug>/).
    • Scopes openid, email, profile. Add a groups scope mapping only if the app needs group-based roles (most don't).
    • Subject mode: leave the default (hashed_user_id). External apps don't need the portal's UUID-subject requirement — that's portal-only.
  • Application wrapping the provider, slug = <app>. Leave its launch URL and icon empty — the portal owns the tile's URL and icon, not Authentik.
  • Policy binding (optional) — bind the group(s) allowed to sign in. This is the real access gate; see Access control.

Step 2 — point the app at Authentik

Configure the app with the provider's values. For ExcaliDash that's environment variables:

AUTH_MODE=oidc_enforced            # OIDC only; no parallel local accounts
OIDC_ISSUER_URL=https://auth.internal.shyftsolutions.io/application/o/excalidash/
OIDC_CLIENT_ID=<from the provider>
OIDC_CLIENT_SECRET=<from the provider>
OIDC_REDIRECT_URI=<app-origin>/api/auth/oidc/callback
OIDC_SCOPES=openid profile email

Step 3 — register the tile in the portal

/admin/apps/newType: Authentik app (OIDC direct).

  • External launch URL (the Base URL field, relabeled): the app's own public origin — where the tile points. No /apps/... path here; that's the proxied flow only.
  • Display name, Icon URL, Category as you like.
  • Visible to: Everyone or Specific groups (this controls the tile, not access — read on).

Role mapping and the health-check path are hidden for this type: the portal doesn't mint roles for it and doesn't poll it.

Access control — read this

Hiding the tile is not access control

For an Authentik OIDC app, the portal tile's visibility only decides who sees the tile on the home grid. It does not decide who can sign in. The real gate is a policy binding on the Authentik application. If there's no binding, every authenticated Authentik user can sign in — even if you scoped the tile to one group, the app's URL is still directly reachable.

So there are two independent knobs, and you usually want them to agree:

IntentPortal tile visibilityAuthentik policy binding
Open to everyoneEveryonenone (any authenticated user)
Restricted to a groupSpecific groups (that group)bind that group on the application

If you only set the portal tile to Specific groups but skip the Authentik binding, you've made the app look restricted while leaving it open. Set both.

Worked example: ExcaliDash

ExcaliDash is our first OIDC-direct app, and it's intentionally open to all authenticated users — that's how it runs, permanently. So there's deliberately no policy binding on its Authentik application, and its portal tile is set to Everyone. The two knobs agree: open everywhere.

Type:                 Authentik app (OIDC direct)
Display name:         ExcaliDash
External launch URL:  http://internal-dev.shyftsolutions.io:6767   # dev; prod is an https subdomain
Visible to:           Everyone
Icon URL:             <a logo if you have one>

Once registered, the ExcaliDash tile shows for every signed-in user, opens in a new tab, and sends them through Authentik. GET /apps/excalidash/… returns 404 — there is no proxy path for an external tile.

Why no binding for ExcaliDash

We decided ExcaliDash should be available to everyone in the org, forever — so a group binding would just be maintenance with no benefit. The restriction pattern above is there for the next app that does need to be scoped.

Visibility: everyone vs. specific groups

The Visible to toggle only appears for the two external types.

  • Everyone — visible to any signed-in user. An empty allowed-groups list is fine.
  • Specific groups — the tile is filtered to members of the groups you pick (same allowed-groups control as internal apps).

Internal (Shyft-auth) apps don't get this toggle: they're always group-gated, because they need a role mapping to be proxied at all. For those, "who sees the tile" and "who can use it" are the same thing — the portal enforces it. For external apps they're separate, which is exactly why the access-control warning matters.

FAQ

Can an external app be served under /apps/<slug>/ like internal ones?

No. External tiles link straight to the app's own origin and are never proxied — /apps/<slug>/ 404s for them on purpose. Many off-the-shelf apps (ExcaliDash included) can't be hosted under a subpath anyway; give them their own host/subdomain.

The app supports OIDC but I want to skip the Authentik setup — can I just use a Third-party link?

You can — a link tile will happily point at it. But then the portal makes no auth claims and you lose the "register it as one of our Authentik applications" benefits (consistent SSO session, central audit, the option to bind a group later). Prefer Authentik app (OIDC direct) for things we host.

I scoped the tile to a group but people outside it can still get in.

That's the access-control gotcha: the tile filter is cosmetic for OIDC apps. Add a policy binding on the Authentik application for that group — that's what actually blocks sign-in.

Do external tiles show a health dot?

No. The portal doesn't poll external origins, so the tile shows a neutral External marker and an "Open ↗" affordance instead of the live/down dot that internal apps get.