Skip to content

Sign in with HRTG

A standard OpenID Connect provider where every sign-in is a biometric approval on the person's own phone. Your existing library works; there is no SDK to install.

Beta

Last updated October 2, 2026

Sign in with HRTG is in beta testing. Access is limited to beta testers: request it before you build, and expect details here to change.

At a glance

Issuer
https://api.hrtg.me
Discovery
https://api.hrtg.me/.well-known/openid-configuration
Flow
Authorization code with PKCE (S256)
Scopes
openid required; email, profile
ID token
RS256 by default; EdDSA on request
Client authentication
client_secret_basic, client_secret_post or private_key_jwt; none with PKCE for apps without a secret
Authorization code
60 seconds, single use
Access token
5 minutes, reads /oidc/userinfo only

How it works

  1. 1

    Your app sends them to HRTG

    A standard authorization code request with PKCE. Your library builds it.

  2. 2

    HRTG asks who is signing in

    There is no password and no login cookie at HRTG. Their browser shows a four-character code.

  3. 3

    They approve on their phone

    Face ID or a fingerprint, once they have checked the code on the phone matches the one in the browser.

  4. 4

    Your app receives an ID token

    Your library exchanges the code and verifies the token. You create your own session.

Your phone will show this code

R4TX

If it does not match, refuse on your phone. Someone else started this sign-in.

Example

You receive identity, never the vault. The ID token and userinfo carry who the person is, and only the claims you asked for. Nothing the person keeps in their HRTG vault is ever available to your application.

The one practical difference from other providers: the middle step takes as long as a person takes. They have to pick up their phone. A few patterns you may be used to are not available, so start with Confirm HRTG fits your app.

Confirm HRTG fits your app

Read this before you design your session handling.

Not availableWhyWhat to do instead
Silent renewal / hidden-iframe refreshThere is no login cookie for us to check without asking the personHold your own session and set its lifetime to suit you
Refresh tokensWe only issue authorization codesStart a new sign-in when your session ends
Single sign-on across applicationsEvery sign-in is a fresh approval on their phoneExpect people to approve once per application
Signing someone out everywhereWe have no way to reach your session or anyone else’sEach application signs out on its own

If your product depends on silent renewal or on signing people out of everything at once, HRTG is not the right provider for it.

Browser-only apps: the sign-in flow works, but turn off automaticSilentRenew, checkSessionInterval and anything iframe-based. They rely on a capability we do not have and will retry forever.

Not supported at all: implicit flow, hybrid flow, client credentials, password grant, device code.

Request your client

Registration is by request. Send us the details below and we will issue your credentials. A person reviews every registration, because a mistake in a redirect URI is the one thing that can turn a sign-in endpoint into a security hole.

DetailWhat to send
Application nameWhat the person sees when they approve on their phone. Use the name they know your product by
WebsiteYour host, no https:// and no path: acme.example
Redirect URIsEvery URI you need, exactly as your library sends it. See Register exact redirect URIs
What you need to know about themTheir name, their email address, or neither
Where your app runsIn a browser or on a phone: public. On your own server: confidential. For private_key_jwt, include your public key set
Related applicationsAny other applications that must recognise the same person as one account. Decide now: this cannot be changed once anyone has signed in
LogoSquare, at least 192×192. Optional: we show a monogram if you have none

Email the details to hello@hrtg.me. The link opens a message with the fields ready to fill in.

What you receive

  • A client ID, for example acme-portal.
  • A client secret, but only if your application runs on your own server. We show it once and never again: we store only a hash of it and cannot recover it. Put it in a secret manager immediately, never in your repository.

Register exact redirect URIs

This is the most common thing to get wrong.

https://acme.example/callback and https://acme.example/callback/ are different URIs. So are …/callback and …/callback?next=/home. Send us the exact string your library will use, trailing slash and all.

  • HTTPS is required.
  • http://localhost and http://127.0.0.1 are allowed on any port, for local development and for native apps.
  • Wildcards are never accepted. Register each URI you need, up to ten.

Register your development and production URIs at the same time so you are not blocked later.

Configure your library

Next.js with Auth.js (NextAuth)

auth.ts

// auth.ts
import NextAuth from 'next-auth'

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    {
      id: 'hrtg',
      name: 'HRTG',
      type: 'oidc',
      issuer: 'https://api.hrtg.me',
      clientId: process.env.HRTG_CLIENT_ID,
      clientSecret: process.env.HRTG_CLIENT_SECRET, // omit for a browser-only app
      checks: ['pkce', 'state'],
      authorization: { params: { scope: 'openid email' } },
    },
  ],
})

Sign-in button

<button onClick={() => signIn('hrtg')}>Sign in with HRTG</button>

Your redirect URI will be https://your-app.example/api/auth/callback/hrtg. Register that exact string.

Any other library

Every conformant library takes the same handful of values. Point it at the discovery URL in At a glance and it configures itself, then set your client ID, your client secret if you have one, your redirect URI, and the scopes you need.

Settings libraries commonly get wrong

PKCE must be on for any app without a secret: browser and mobile apps. Server-side apps holding a secret may leave it off, though we recommend it. If a browser app gets invalid_request at sign-in, this is almost certainly why.

Leave the ID token algorithm as RS256. We also offer EdDSA, but Spring Security and Microsoft’s libraries cannot verify it and fail with an error that does not say so.

Server-side apps choose how they prove who they are. client_secret_basic and client_secret_post both work with your secret. private_key_jwt signs a short assertion with your own key instead (RS256, ES256 or EdDSA), so no shared secret exists at all; send us your public key set at registration. Set the assertion's aud to our issuer, https://api.hrtg.me. The token endpoint URL is accepted too, but the issuer keeps your assertion from being replayed at another server.

Key accounts on sub

You asked forYou receive
openidsub: a stable identifier for this person, at your application
profilename
emailemail, and email_verified (always true: we hold no unverified address)

You also get auth_time: the real moment they approved on their phone, not when the token was issued.

The same person gets a different sub at every application. Yours cannot be matched against anyone else’s records, and theirs cannot be matched against yours. That is deliberate.

For your application it never changes, including when the person replaces their phone or recovers their account.

Store sub. Do not key accounts on the email address. People change their email, and if that is your key you will strand their account.

If you run several applications that need to recognise the same person as one account, say so in your registration (see Request your client). This cannot be changed once anyone has signed in.

Run your own session and sign out locally

Once you have verified the ID token, create your own session and manage it yourself. That is the whole integration.

The access token we issue lasts five minutes and reads exactly one endpoint, the standard /oidc/userinfo. It is not a key to an HRTG API. There isn’t one for applications to call. Read anything you need during sign-in and you are done.

Signing out is local to your application. Clear your session. The person stays signed in to their other applications, and we have no way to reach them.

Give the callback time

Someone has to pick up their phone, so a sign-in takes as long as a person takes. Do not set a short timeout on your callback route.

A sign-in that seems to hang in testing is waiting for someone to approve on a phone.

Before you go live

When something goes wrong

Our token endpoint returns the same invalid_grant for every problem with a code: wrong, expired, reused, or sent with a verifier or redirect URI that does not match. Telling them apart would help an attacker guess, so the message will not narrow it down. A bad or missing secret is reported separately, as invalid_client. Work through this instead:

What you seeAlmost always
An error page on HRTG’s own site, no redirect backYour client ID is unknown, or the redirect URI does not match exactly. We will not redirect to a URI we cannot verify
invalid_grant when exchanging the codeThe code_verifier does not match the sign-in (or was sent for a sign-in that had no PKCE); or the redirect URI you sent when exchanging differs from the one you sent at sign-in; or the code is more than 60 seconds old
invalid_clientWrong secret, or a server-side app sending no secret at all
login_required, immediatelyYou sent prompt=none. It can never succeed here
invalid_scopeYou left out openid. Other scopes your application is not registered for are dropped rather than refused; the token response’s scope lists what you were given
Sign-in “hangs” in testingSomeone has to approve on a phone. It is not hung

Codes expire in 60 seconds and work once. Presenting one twice looks exactly like an attack, so we revoke the session immediately. If an exchange fails, start a new sign-in rather than retrying with the same code.

Getting help

Contact us at hello@hrtg.me with your client ID and roughly when the problem happened. For changes to a registration, the same route applies: a new redirect URI, a new scope, a rotated secret.