Developers

Sign in with URnetwork

6 min readView as markdown ↗

Let people admit your app or agent to their private network, so it can coordinate with everything else they have running there.

The Sign in with URnetwork button, in both variants: the dark button on a light surface, the light button on a dark surface

Download the button kit — markup, styles, both variants as single assets, and the mark on its own. The kit is final and you can adopt it today, ahead of everything else on this page.

In development — not yet live. This page describes a design under construction. The authorization server is written but not deployed: auth.bringyour.com does not resolve yet, and none of the endpoints below can be called today. client_ids are issued on request rather than self-serve. Everything marked planned is not built. The button kit is real and final, and you can adopt it now.

What it is

Signing in is the admission step. What it grants is a place on a network — addressable, discoverable by the person's other software, and revocable by them at any time.

That makes it a different thing from the sign-in buttons it resembles. You are not primarily learning who somebody is. You are being let into somewhere.

The grant has two halves, consented and revoked separately:

  • Identity — that a URnetwork account approved you, and nothing more.
  • Network — which network you may act on, named on the consent screen and bound into the token when it is issued.

An app may ask for identity alone. Network access is never implied by signing in.

The button

Use the dark button on light and mid-tone surfaces, the light button on dark ones. There is no hosted script: the kit is HTML, CSS and one inline SVG, so placing the button on your page transmits nothing until somebody clicks it.

Prefer the markup below over the SVG assets where you can — the label then renders in your page's own type and stays selectable, searchable and translatable. The single-asset SVGs in the kit are for places that take an image and nothing else.

The button reads Sign in with followed by the URnetwork lockup. Only the first part is text, which is what makes the button translate cleanly: render "Sign in with" in your interface language and leave the lockup alone, so the brand reads the same everywhere.

<a class="ur-signin" href="YOUR_AUTHORIZE_URL" aria-label="Sign in with URnetwork">
  <span>Sign in with</span>
  <svg class="ur-signin-lockup" viewBox="0 0 980 139" fill="currentColor" aria-hidden="true">
    <!-- the lockup, 10 paths — copy them from the kit -->
  </svg>
</a>

The lockup is drawn with currentColor, so it inherits the button's text colour and one copy serves both variants. The aria-label carries the whole name, since half of it is a graphic.

.ur-signin {
  display: inline-flex; align-items: center; gap: 10px;
  height: 44px; padding: 0 20px; min-width: 200px;
  border-radius: 12px; border: 1px solid transparent;
  background: #101010; color: #f8f8f8;
  font: 500 15px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
.ur-signin:hover         { background: #1c1c1c; }
.ur-signin:focus-visible { outline: 2px solid #0099ff; outline-offset: 2px; }
.ur-signin-lockup        { height: 16px; width: auto; flex: none; }

/* Light variant, for dark surfaces */
.ur-signin.is-light       { background: #f8f8f8; color: #101010; border-color: rgba(16,16,16,.14); }
.ur-signin.is-light:hover { background: #ececec; }

The label uses a system stack rather than the URnetwork brand face, which cannot be redistributed to other sites — and pinning a webfont would put a network request back into the button.

Brand rules

Height44px minimum — also the tap target. Never smaller.
Corner radius12px
Lockup16px tall, currentColor, never redrawn, recoloured or rebuilt from parts
Clear space8px on all four sides
Focus ring2px #0099ff, offset 2px
Ground#101010 dark, #f8f8f8 light. No other colour, no gradient.

Do not set the word URnetwork in your own type in place of the lockup, do not use the mark without the label, and do not animate or skew the button. Translate "Sign in with" into your interface language; the lockup never changes.

How it works

Planned. The endpoints below are not deployed.

Authorization code with PKCE, and nothing else — this is OAuth 2.1, so there is no implicit grant and S256 is mandatory.

Issuerhttps://auth.bringyour.com
Discovery/.well-known/openid-configuration and /.well-known/oauth-authorization-server
Keys/.well-known/jwks.json
Authorizehttps://ur.io/authorize
TokenPOST /oauth/token
UserInfoGET /oauth/userinfo
RevokePOST /oauth/revoke

The authorization endpoint sits on a different origin from the issuer on purpose. The consent screen has to reuse the signed-in session, and browser storage is scoped to an origin, so the page lives where the session already is. RFC 8414 permits this and the issuer identity is unaffected — keep validating iss against auth.bringyour.com.

Consent is always shown the first time you ask for a given set of scopes, even when the person is already signed in. Approving a third party silently would make the page a confused deputy.

Joining the network

Planned.

An OAuth access token authorizes; it does not speak the connect protocol. To take up the place on the network you were granted, exchange it:

POST /network/auth-client
Authorization: Bearer <access token>

You receive a platform JWT carrying a clientId — the address the protocol knows you by. From there you are an ordinary member of that network, and /network/peers is how you discover what else has been admitted.

Three things surprise people:

  1. This is the only crossing between the two credential systems, and it is an authorization decision made server-side rather than one token that verifies in two places. The signing key sets stay disjoint.
  2. Provisioning a client bills the network. The consent screen says so; it is repeated here so nobody learns it from an invoice.
  3. The credential is scoped to the one network bound at authorization. It never follows somebody to another network.

When two apps need to know they are serving the same person, the shared handle is the network they were both admitted to — not the person. That is deliberate: a network is consented, named on screen and revocable, where a durable user identifier would be none of those things.

Tokens

Planned.

TokenLifetimeNotes
Access1 hourAudience-bound to one resource
Refresh90 days, slidingOpaque and rotating — each use retires the last
ID token1 hourAddressed to your client_id; never send it to a resource server

Refresh tokens rotate, and reuse of a retired one is treated as theft: the whole token family is revoked. Store the newest one you were given.

Requirements for your app

  • PKCE with S256. Not optional.
  • Exact-match redirect URIs, registered in advance, HTTPS only. Native apps may use http://127.0.0.1:/….
  • Verify iss on the authorization response — this is what defends against mix-up attacks.
  • Verify aud on the ID token equals your client_id.
  • Do not present a Sign in with URnetwork token to the Operator API. The key sets are disjoint; it cannot work, and that is the design rather than a gap.

Getting a client_id

Issuance is reviewed, not open. An admitted app can address somebody's devices, so clients are vetted before they can ask for identity or network access. Dynamic registration stays available for MCP-only scopes, where the reach is a provider lookup.

There is no self-serve console yet. Ask for a client_id and we will issue one.

Revoking

Planned.

People manage grants from the connected-apps screen in the URnetwork app, and revoking one removes the network clients it provisioned. What your app observes is the connect session dropping and the clientId no longer resolving — a revocation, not a fault.

Access tokens live an hour, so a revocation takes effect within the hour rather than instantly.