Skip to main content

Apps without SSO support

Secure web authentication fills a login form from a vaulted password. What it solves, how the credential is protected, and where it is weaker than real SSO.

Written for whoever runs IT8 min readUpdated

Every organisation has two or three applications that will never support single sign-on. A parts supplier's ordering portal. A council planning system. Something written in 2011 that the business cannot function without. They have a username box, a password box, and no federation of any kind, and no amount of asking will change that.

Secure web authentication — SWA, the industry's term — is the last-resort mechanism for those. Since the application cannot be told who somebody is, the alternative is to hold each person's own password for it and have the browser fill the login form on their behalf. This article covers what that solves, how the password is protected, what is and is not recorded, and where it is genuinely weaker than federation. It is a fallback, not a goal.

The problem this actually solves

When an application has no single sign-on, what happens in practice is a shared password. One account given to five people, in a shared note, unchanged for four years, still known to the two who left. It has three properties that make it worse than it looks: you cannot tell which of the five did something, removing one person's access means changing it for all five so nobody does, and it will be reused elsewhere, because a password five people memorise is by definition a memorable one.

SWA replaces that with one credential per person, stored encrypted, never displayed, revocable individually. Each person keeps their own account at the application, so its own records still name the right person. That is a real benefit — but smaller than the phrase single sign-on implies, which is why the honest limits get their own section below.

How it works

There are deliberately two separate objects, and the split is the security design of the feature.

The app
An administrator-owned description of a login form: the login URL, and the CSS selectors identifying the username field, the password field and the submit control. It carries no secret material at all, which is what makes it safe for every member of the organisation to read.
The credential
One row per app and per person: that individual's username and password for that application, encrypted with a fresh 256-bit data key wrapped by a key scoped to your deployment. Nobody's credential is readable by anybody else, including an administrator.

The selectors default to input[name="username"] and input[name="password"], which is right for a surprising proportion of older forms. An empty submit selector means submit the form the password field belongs to rather than clicking a named button. Extra fields hold static hidden inputs identical for everybody — a tenant name, a site code — so they are shared configuration and never secret.

The login URL is validated when you save it and again every time somebody launches the app. It must be absolute, http or https, with a host. A relative path has no origin, so the filler would resolve it against whatever page happened to be open. A javascript: or data: URL is not a page at all — it is script that would run in a page a password is then typed into. Re-validating on the way out matters too: a row saved before a rule tightened must not be handed out because it is already stored.

What the user sees

A catalogue of app tiles, each showing whether that person has a credential saved and under which username. Setting one is a single form. Launching produces a descriptor containing the form details plus that person's username and password, which the browser uses to fill and submit the login form.

The plaintext password appears in exactly one place in the system: the launch response, and only for the person calling it. The caller's identity is taken from their session token and never from the request body — a body-supplied user id would let anybody holding the credential-write permission read out a colleague's vaulted password. Every other read path returns the app description, which has no password or ciphertext field at all. The launch descriptor must never be logged, cached or written down.

How the password is stored

Envelope encryption, using the same scheme the privileged access vault uses — deliberately the same one rather than a second implementation, because two crypto paths means two places to get it wrong and only one gets reviewed. Each credential row gets its own randomly generated data key, wrapped by a master key scoped to your deployment. The wrapper derives a distinct wrapping key per namespace, so app-vault material cannot decrypt a privileged credential or the other way round. The credential vault describes the same machinery from the privileged-access side.

The behaviour when the key is missing is the right one, and it is not what most software does. With no encryption key configured, the service refuses to store or return a credential rather than degrading to storing it in the clear. The catalogue keeps working; the vault does not. You will see:

credential vault is not configured
HTTP 503 from any credential or launch endpoint

Permissions and endpoints

The SWA surface. Every path is org-scoped, so it is pinned to your tenant structurally as well as by permission. The permission split is the security model, not bureaucracy.
EndpointPermissionWhy that level
GET /organizations/{orgID}/swa-apps and .../{appID}iam:swa_apps.readThe catalogue holds no secrets, so viewer and member both hold this.
POST and PUT /organizations/{orgID}/swa-apps[/{appID}]iam:swa_apps.writeAdmin only. The login URL names the page a password gets typed into, so whoever can edit it can build a harvesting page.
DELETE /organizations/{orgID}/swa-apps/{appID}iam:swa_apps.deleteAdmin only, and it cascades to every vaulted credential for that app.
PUT and DELETE .../{appID}/credentialiam:swa_credentials.writeSetting or clearing the caller's own credential. Granted to member so ordinary staff can use the catalogue.
POST .../{appID}/launchiam:swa_credentials.writeThe one endpoint that decrypts. It is a POST rather than a GET so it cannot be produced by a link prefetch or cached by an intermediary — but note it is gated on a write permission, so a read-only user cannot launch an app even to use it.

A lookup is always by identifier and organisation together; by identifier alone, never. That is what keeps one customer's catalogue out of another's.

Validation messages, verbatim — all HTTP 400
swa: invalid application: no application supplied
swa: invalid application: name is required
swa: invalid application: username_selector is required
swa: invalid application: password_selector is required
swa: invalid application: login_url is required
swa: invalid application: login_url is not a URL: <err>
swa: invalid application: login_url must be an absolute URL, got "<s>"
swa: invalid application: login_url scheme "<scheme>" is not supported; use http or https
swa: invalid application: login_url has no host: "<s>"
swa: invalid application: icon_url must be an absolute http(s) URL
swa: invalid application: application "<name>" is disabled
swa: invalid credential: username is required
swa: invalid credential: password is required
Other responses you may see
400  could not read the request body
400  invalid request body
401  authentication required
404  not found
503  credential vault is not configured

An empty password is rejected rather than quietly accepted, because silently vaulting one produces a tile that fails on every launch with no explanation.

What is audited

Nothing, and you should know that before you rely on this for anything sensitive.

The platform has a shared audit log that identity, detection, endpoint and privileged-access events all write into. The secure-web-authentication code writes to none of it. Creating an app, saving a credential, deleting one and — most significantly — launching an app and decrypting a password all pass without leaving an audit row. The launch endpoint is the one place a plaintext secret leaves the vault, which makes it exactly the event you would want recorded, and it is not.

Where this is weaker than real single sign-on

A form fill is not authentication. It is automation of a password that still exists, and every consequence of that follows.

  • The password is still a password at the far end. It can be guessed, phished on a lookalike page, and is subject to whatever policy that application has — which for applications needing SWA is usually none.
  • We cannot end a session there. Deleting a vaulted credential stops the fill; it does nothing to a session already open, and nothing to the person who read the password off their own screen last week.
  • Offboarding is two jobs, not one. Deleting the credential here removes the convenience. The account at the application still exists and still works, somebody has to disable it there as well, and no part of Qorionix will remind them.
  • No second factor is involved. A federated sign-in can be made to require one. A form fill types a password into a form. If the application has its own second factor, the fill stops at that prompt — correct, but not integration.
  • A change to the login page breaks it, and can be dangerous. Selectors describe a page you do not control. A redesign turns the fill into a silent failure; a compromised or redirected login page turns it into a password submitted to somebody else. This is why editing an app is admin-only.

The order of preference is unchanged: federate if the application can federate at all, in any form. Use SWA only for the ones that genuinely cannot, and treat each of those as a candidate for replacement.

App assignments

There is a separate feature for recording which users and groups are entitled to which applications, with a status, a mode saying whether the application uses SAML, OIDC, SCIM or nothing, and a place for provisioning settings. You can create, list, update and delete these records, gated on the user permissions.

Two smaller sharp edges. The four modes — saml, oidc, scim, none — are documented only in a comment on the database table; nothing validates the value, so any short string is accepted. And an assignment naming neither a user nor a group is refused by the database rather than the handler, surfacing as a server error carrying the raw constraint name. Always send either a user or a group.

What to do next

  1. List the applications your staff share a password for. That list is the real problem, and it is usually shorter than people expect and worse than they remember.
  2. Check each one again for SAML or OIDC — many older products added it quietly. Single sign-on with SAML, Single sign-on with OIDC.
  3. For anything both unfederatable and sensitive, use the privileged access vault, so retrieval is recorded. The credential vault.
  4. For the rest, give each person their own account at the application and their own vaulted credential here, then delete the shared one.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in identity and access