Repository navigation
feat: browser sign-in for the UI (Microsoft, administrator password, access token) - #17
Merged
Merged
Conversation
…access token)
The UI had no way to sign anyone in: the UI server put an administrator token on
every API call it forwarded, so opening its address was enough to be admin, and
there was no login page to speak of.
Give each way in an address of its own. /login is the default page and carries
Sign in with Microsoft, through an OAuth flow the server hosts end to end, so the
client secret never reaches the browser. /login/admin takes the password from
server.admin-password, and /login/token takes an access token this server issued,
for whoever runs it. None is linked from another, the application itself is no
longer an entry point -- without a session it redirects to /login -- and the UI
server adds no credentials of its own. Which ways exist is asked of the server at
runtime, so one UI build follows any deployment's configuration.
Accepting an identity provider's tokens needed the server side to change too:
- the static JWKS file and OIDC discovery now coexist, routed by whether the
issuer is registered in the file, so a Relyt instance and Entra ID are both
trusted at once
- the caller resolves through an ordered claim chain (email, preferred_username,
upn, sub), because Entra omits email unless it is configured as an optional claim
- discovered providers are cached per issuer and every call to the provider is
bounded; an unreachable one answers 401 with its name rather than a bodyless 500
- Entra's {tenantid} issuer template is compared segment-wise, which multi-tenant
endpoints advertise literally
- the session cookie is one a browser keeps: Secure only when the browser is on
HTTPS, read the way the callback URL is read, and SameSite=Lax, since the browser
returns from the provider on a cross-site navigation
- the URL transcoder passes a redirect back instead of following it, and ends a
bodyless response on its headers
Deployment follows: deploy-uc.sh derives the audience and, where the authorization
URL states it unambiguously, the issuer; it starts the UI alongside the server, and
warns when it is about to render secrets over a file that git tracks. The Entra
setup, including what the client secret's expiry does and how to rotate it without
losing a sign-in, is documented in both deployment guides.
The known-gap note it removes is what this PR closes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The UI had no way to sign anyone in. The UI server put an administrator token on every API call it forwarded, so opening its address was enough to be admin, and there was no login page to speak of.
This gives each way in an address of its own:
/login— the default page, Sign in with Microsoftserver.authorization-url…server.client-secret/login/admin— a password form, not linked from the login pageserver.admin-password/login/token— paste an access token this server issuedThe application is no longer an entry point: without a session it redirects to
/login. The UI server adds no credentials of its own. Which ways exist is asked of the server at runtime (GET /auth/providers), so one UI build follows any deployment's configuration.Microsoft sign-in runs through the server: it redirects to the provider, redeems the code itself and sets the session cookie, so the client secret never reaches the browser.
Server side
Accepting an identity provider's tokens needed more than an entry point:
email,preferred_username,upn,sub— because Entra ID omitsemailunless it is configured as an optional claim. All four are looked up as the user's email; the authorization model is untouched.{tenantid}issuer template is compared segment-wise, which multi-tenant endpoints advertise literally.Secureonly when the browser is on HTTPS, andSameSite=Laxrather thanStrict— the browser returns from the provider on a cross-site navigation, exactly the request aStrictcookie is withheld on.invalid_client, AADSTS7000222), which is what separates an expired secret from a mismatched redirect URI or a replayed code; the full description is logged at warn.Deployment
deploy-uc.shderives what the token check needs from the four Microsoft settings — the audience always, the issuer when the authorization URL states it unambiguously — and prints what it added. It starts the UI alongside the server, and warns before rendering secrets over a file that git tracks. Both deployment guides document the Entra setup, including what the client secret's expiry breaks and how to rotate it without losing a sign-in; the Chinese guide had no sign-in chapter at all.Tests
Server 83 (dual trust chains, identity resolution, issuer templates, discovery caching, hosted login, administrator sign-in configured and not, token sign-in, providers), UI 82 across 22 suites including the route table.
javafmtCheckAll,yarn test:formatandyarn buildare clean.Verified against a real Entra tenant end to end: the redirect carries the registered client id and callback, and a signed-in user's catalog and table lists are filtered by their grants.
Note for review
This touches
JwksOperationsandAuthService, which #15 also changes. Whichever lands second will need a rebase there; the rest of this change does not overlap.