Connect an app
Brionic ID is a plain OpenID Connect provider. If your framework already has an OIDC client, you do not need this page: point it at the discovery document and it will configure itself.
https://id.brionicx.com/.well-known/openid-configuration
Everything below is read from that same document, so the two cannot disagree.
Endpoints
| Purpose | URL |
|---|---|
| Issuer | https://id.brionicx.com |
| Authorization | https://id.brionicx.com/authorize |
| Token | https://id.brionicx.com/token |
| User info | https://id.brionicx.com/userinfo |
| Public keys | https://id.brionicx.com/.well-known/jwks.json |
| Sign out | https://id.brionicx.com/logout |
What it accepts
-
Authorization code with PKCE —
S256only, and required of every client including confidential ones. There is no implicit flow, no hybrid flow and no password grant, because each of them hands a token to something that cannot keep it. -
Refresh tokens — issued only when you ask for the
offline_accessscope. They rotate on every use, and a token presented twice is treated as theft: that whole chain is revoked rather than refreshed. -
Client authentication —
client_secret_basic, client_secret_post, none.
Use
nonefor anything that runs on a device you do not control, such as a single-page app or a mobile client, and rely on PKCE. -
Scopes —
openid,profile,email,offline_access. A client is registered with the scopes it may ever ask for, and asking for more is refused rather than trimmed. -
Subjects — pairwise by default, so the same person gets a
different
subin each application and two applications cannot compare notes. First-party apps can share a subject group when they are meant to. -
A provider hint — add
idp=to your/authorizerequest and someone who has to sign in goes straight to that provider instead of being shown our chooser. Use it when your own page already offered the choice, so a button saying "Sign in with Apple" behaves like one:…/authorize?client_id=…&idp=apple. Accepted values are the providers on our sign-in page — currentlygoogle,microsoft,apple. A hint we do not recognise is ignored rather than refused: the person still gets a working sign-in page. The hint is spent once used, so it never survives into the request we resume you with.
Getting credentials
Sign in and open Developer to register your own application. You will provide:
- A name — what the person is told they are signing in to.
- Redirect URIs — matched exactly, in full. Not by prefix, not by wildcard.
- Confidential or public — whether your app can actually keep a secret.
- Scopes — the smallest set you can work with.
A confidential client's secret is shown once, at creation, and only its digest is kept. Nobody can read it back to you afterwards, so store it before you close the window.
The exchange, end to end
1. Send them to sign in
https://id.brionicx.com/authorize ?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback
&scope=openid%20email%20profile
&state=RANDOM_PER_REQUEST
&nonce=RANDOM_PER_REQUEST
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
Keep state and the PKCE verifier in the session. Check state
when they come back, or you have built an open door for someone else's authorization code.
2. Swap the code for tokens
curl -X POST https://id.brionicx.com/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=THE_CODE \
-d redirect_uri=https://your.app/callback \
-d code_verifier=THE_VERIFIER
3. Verify the ID token
Fetch the keys from https://id.brionicx.com/.well-known/jwks.json, then check the
signature and these claims before you trust anything in it:
issis exactlyhttps://id.brionicx.comaudcontains your client idexpis in the futurenonceis the one you sent
4. Ask who they are
curl https://id.brionicx.com/userinfo \
-H "Authorization: Bearer ACCESS_TOKEN"
sub is the only identifier that is stable. An address can be changed,
removed, or moved to another account, so key your users on sub and treat
email as a display detail.
Claims you can receive
iss sub aud exp iat auth_time nonce amr azp email email_verified name
amr tells you how they proved who they were on this session, so a
sensitive action can insist on a passkey rather than an emailed link.
auth_time tells you when, so you can ask them to prove it again.
Before you start, two things that catch people
-
ID tokens are signed with
EdDSA,
and nothing else.
Current libraries handle it. Plenty of older ones, and a good number of
off-the-shelf platforms, accept
RS256only and will reject these tokens outright. Check that before planning an integration, not after. - There is no SAML. If a platform only speaks SAML, it cannot use Brionic ID today.
Signing out
Decide which of these two you mean before you write any of it. Almost every app wants the first, and the second is the one that is easy to reach by accident.
| They press sign out in your app | Your app | Their other Brionic apps | Getting back in |
|---|---|---|---|
| Out of your app — start here | signed out | untouched | one press, nothing typed |
| Out of Brionic ID — only if you mean it | signed out | signed out as well | a full sign-in again |
⚠️ Discovery advertises an end_session_endpoint and it reads like the
sign-out endpoint, so it is the one people wire the button to. It is the second row.
Choosing it means somebody who signs out of your app also loses the tab they had open
in another one, which they experience as being logged out of things they never
touched, by an app they were not using.
Unless you can say why this app is the exception, build the first and offer the second somewhere separate, worded so nobody presses it by accident.
Out of your app, which is what you usually want
Destroy your own session and stop. Do not send them here. They stay signed in to Brionic ID, so when they come back and press your sign-in button they are returned to you straight away without typing anything.
That is single sign-on working, not a bug, though it does surprise people the first time. If the point is to hand the device to someone else, use the second kind.
Out of Brionic ID, everywhere
Worth offering on a shared or borrowed machine, and worth naming as such: “Sign out of
everything” rather than “Sign out”. Send them to
https://id.brionicx.com/logout with
id_token_hint and, if you registered one, a
post_logout_redirect_uri. This ends the session itself, so it signs them
out of every other Brionic app as well.
Send the id_token_hint. With a valid one we know which app is asking and
end the session immediately; without it we have to ask the person to confirm first,
because otherwise any page on the internet could sign them out by linking here.
The post_logout_redirect_uri has to be one you registered, matched in
full. An address we do not recognise is ignored rather than refused, and the person
simply lands on our page instead of back on yours, which is the failure people spend
an afternoon on. A state value is handed back on the end of it.
Being told when a session ends elsewhere
Register a logout endpoint and we will POST a signed logout_token to it
whenever a session you hold a grant from ends, whether from the account page, another
app, or an operator. You are told about any sign-in you were given tokens from —
offline_access is not required and never was relevant to this.
Four steps, and the first is the one people miss.
⚠️ If you have not registered an endpoint, you get nothing. There is no error and no warning anywhere: sign-out simply has nowhere to reach you, and the person stays signed in to your app after signing out here. Check yours on the applications page before assuming this works.
-
At sign-in, keep the
sidfrom the ID token on your own session record, beside thesub. Skip this and nothing else here can work: you will be told that a session ended and have nothing to match it against. -
Register your logout endpoint against your application, on the
applications page, so we know where to send it. The same
page is where a
post_logout_redirect_urigoes. -
Check the token before you act on it. It arrives as a form field
named
logout_tokenon an unauthenticated public endpoint, so it is the checks below that make it trustworthy and nothing else. -
End the session whose
sidmatches, and only that one. Reply200. A body is not read.
⚠️ Prove it rather than assume it. Sign in to your app, sign out of Brionic ID from the account page, then reload your app: you should be signed out. A receiver that was never reached looks exactly like a receiver that works, because the only visible symptom is a session quietly staying open.
⚠️ An endpoint that skips step 3 lets anyone who finds the URL sign your users out at will. Verify all of it:
signature verifies against https://id.brionicx.com/.well-known/jwks.json
iss https://id.brionicx.com
aud your client id
typ logout+jwt
events contains "http://schemas.openid.net/event/backchannel-logout"
nonce MUST BE ABSENT — its presence means you were handed an ID token
iat recent, and the same jti twice is a replay
Your sid is yours alone. Two apps are given different values for the same
visit, so neither can use it to work out it is looking at the same person as the other.
⚠️ One attempt, five seconds, no retry. If your endpoint is slow, down, or behind a deploy, that notice is gone and we will not send it again. Treat it as a prompt to end a session early, never as the thing your security depends on: keep your own sessions short enough that one lost message does not matter.
If a token arrives with no sid, it belongs to a grant made before we
issued them. Ending every session for that sub is the safe reading, but do
not make it your normal path: it signs the person out of your app on every device they
own because they signed out of one, which is the behaviour that makes people think
sign-out is broken in the other direction.
An access token already issued keeps working until it expires, which is minutes rather than hours. Nothing can recall one that is already in someone's hands, so treat sign-out as ending the ability to get new tokens rather than as an instant off switch.
Sign in with Apple
You do not need an Apple Developer account, a Services ID, or a signing key. We hold the relationship with Apple and carry the cost of maintaining it, so putting a real Sign in with Apple button on your page is one link.
<a href="https://id.brionicx.com/authorize?client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback
&response_type=code&scope=openid%20email%20profile
&code_challenge=…&code_challenge_method=S256
&state=…&idp=apple">Sign in with Apple</a>
It is an ordinary authorization request with idp=apple on the end. The
person goes straight to Apple, and comes back to your callback the way every other
sign-in does. Nothing else about your integration changes.
If you ship an iOS app, read this part
-
App Store Review Guideline 4.8. An app that offers any third-party
or social login must also offer a login option that limits data collection to name
and email and lets the person keep their email private. Sign in with Apple is the
option reviewers look for. Handing off with
idp=applepresents the genuine Apple sheet, so the person really is signing in with Apple. - Put a real Apple button on your own page. A single "Continue with Brionic ID" button is what gets an app read as offering one way in. Render the Apple button beside your others and point it at the link above.
- The button is Apple's to design, not ours. Its shape, colour, wording and minimum size are set by Apple's Human Interface Guidelines, and the mark may not be redrawn or recoloured. Take the assets from Apple and follow their rules — our button guidance is for the Brionic ID button only and does not apply to Apple's.
Three things about Apple that surprise people
-
The email address may be a relay, and that is fine. Someone who
chooses to hide their address gives you one ending
@privaterelay.appleid.com. It is real, it is verified, and Apple forwards it. Do not reject it, do not ask for a "proper" address, and do not treat it as a throwaway. It stops forwarding only if the person disconnects the app from their Apple Account, which is them withdrawing, not a bad address. - ⚠️ Mail to a relay address bounces unless the sending domain is registered with Apple. That registration lives with the team that owns the Sign in with Apple configuration, which is ours, not yours. If you send mail to addresses you got from us, tell us the domain you send from so it can be added. Skip this and your mail silently fails for exactly the people who cannot tell you it did.
- Apple sends a person's name once, ever. Not on the first sign-in to your app — on the first authorization that human ever makes, which may have happened before your app existed. We capture it when it is offered and give you whatever we hold. If you get no name, ask for one; there is no second chance to read it from Apple.
The sub you receive is ours and is stable for that person in your app, so
none of Apple's own identifiers reach you and nothing changes if we ever alter how we
talk to Apple. Signing in with Apple and signing in with Google makes
two separate accounts, deliberately: an address alone never
links anything here, and it is the person who joins them from their account settings.
The sign-in button
We do not render this — it lives on your page, so none of it is enforced. It is here because you need one, and building it yourself means measuring contrast for a mark you did not draw.
⚠️ The mark is inlined into the markup, not loaded from us. Hotlinking
it would put our uptime in the critical path of your login page, and would tell us who
was looking at it before they had chosen to sign in. Inline SVG needs no request, no
CORS and no change to your img-src.
Themes
Pick one. Each is shown on the kind of page it is meant for.
bid-btn on its own
The default. An outline on a white or near-white page.
bid-btn-soft
Filled, for light pages where an outline reads as too quiet.
bid-btn-dark
For dark pages.
bid-btn-contrast
Near-black, for interfaces whose primary button is black.
There is no brand-blue filled theme, and that is a measurement rather than a taste.
The mark carries its own steel field, so a button filled with the same blue leaves it
at 1.6:1 against its own background and the silhouette dissolves.
Going dark enough to separate arrives at bid-btn-contrast, which is
already in the list.
Shape and width
Both combine with any theme above.
bid-btn-pillFor interfaces with fully rounded controls.bid-btn-blockFor stacked sign-in forms, and the answer to a label that will not fit.Markup
Point href at whatever route in your app starts the exchange, and add a
theme class beside bid-btn if you want one.
<a class="bid-btn" href="/auth/brionic-id">
<svg width="20" height="20" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
<defs>
<linearGradient id="bidbtn-field" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#4682b4"/><stop offset="1" stop-color="#1b3648"/>
</linearGradient>
<radialGradient id="bidbtn-ball" cx=".34" cy=".3" r=".75">
<stop offset="0" stop-color="#ffffff"/><stop offset=".5" stop-color="#dbe8f2"/><stop offset="1" stop-color="#7f9db3"/>
</radialGradient>
<radialGradient id="bidbtn-core" cx=".34" cy=".3" r=".75">
<stop offset="0" stop-color="#fdf0c0"/><stop offset=".45" stop-color="#e8bf4c"/><stop offset="1" stop-color="#9c7405"/>
</radialGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#bidbtn-field)"/>
<g fill="none" stroke="#cfe0ee" stroke-width="5" stroke-linecap="round">
<path d="M35.42 22.6 38.16 15.08"/>
<path d="M38.43 39.66 43.57 45.79"/>
<path d="M22.15 33.74 14.27 35.13"/>
</g>
<g fill="url(#bidbtn-ball)">
<circle cx="38.16" cy="15.08" r="8"/>
<circle cx="43.57" cy="45.79" r="8"/>
<circle cx="14.27" cy="35.13" r="8"/>
</g>
<circle cx="32" cy="32" r="10.5" fill="url(#bidbtn-core)"/>
</svg>
Continue with Brionic ID
</a>
Styles
Self-contained, so it inherits nothing from your stylesheet or ours. This is read straight out of the stylesheet this page is using, so the two cannot disagree.
.bid-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
min-height: 44px;
padding: 0.7rem 1.15rem;
font: 600 0.95rem/1.2 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: #0d1c27;
background: #ffffff;
/* 3.85:1 against white. The border is the only thing describing the
button's edge, so it has to meet non-text contrast on its own. */
border: 1px solid #6d8598;
border-radius: 0.6rem;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
.bid-btn:hover {
background: #f4f9fc;
}
.bid-btn:focus-visible {
outline: 2px solid #1f6193;
outline-offset: 2px;
}
.bid-btn svg {
flex: none;
}
/* Themes. Deliberately classes and not prefers-color-scheme: the host page may
be light while the visitor's system is dark, and a button that followed the
system would go black on a white page. Only the app knows which surface it
put us on.
⚠️ Each theme's edge was measured against the page it is meant for, not
against its own fill. On the pale themes the border is the only edge, so it
carries the contrast; on the dark themes the fill already separates and the
border is there for dark pages. Both pale themes use the same border for
that reason, even though it looks light against the soft fill. */
/* Filled, for light pages that want more presence than an outline. */
.bid-btn-soft {
background: #e4ecf3;
}
.bid-btn-soft:hover {
background: #d3e0ea;
}
/* For dark pages. */
.bid-btn-dark {
color: #e6eef5;
background: #10242f;
border-color: #557f99;
}
.bid-btn-dark:hover {
background: #16303d;
}
.bid-btn-dark:focus-visible {
outline-color: #79bce8;
}
/* Near-black, for interfaces whose primary button is black. */
.bid-btn-contrast {
color: #ffffff;
background: #0d1c27;
border-color: #5a7183;
}
.bid-btn-contrast:hover {
background: #16303d;
}
.bid-btn-contrast:focus-visible {
outline-color: #79bce8;
}
/* Shape and width, combinable with any theme above. */
.bid-btn-pill {
border-radius: 999px;
}
/* Sign-in forms usually stack full-width controls. This is also the answer to a
label that will not fit: give it the width, rather than shrinking the text. */
.bid-btn-block {
display: flex;
width: 100%;
}
- Say “Continue with Brionic ID” — or “Sign in with Brionic ID”. Do not shorten it to “Brionic” or “ID”.
- The theme is a class you choose, not a media query. Your page may be light while the visitor's system is dark, and a button that followed the system would go black on a white page.
- Do not make it smaller. It is 44px tall because that is the smallest comfortable tap target, and the mark stops being legible below 18px.
- Keep the border on the pale themes. Their fill barely differs from a white page, so the border is the only thing describing the button's edge.
The mark, the palette and the rules for using it are on the brand page.
That is the whole protocol surface
The discovery document is the reference. This page only explains it.
Open the discovery document