TatvaOS · Applications

SSO Integration Guide

Let people sign in to your application with their TatvaOS account. It uses OpenID Connect, the same standard as "Sign in with Google". If you have never done this before, read from the top. It is shorter than it looks.

  1. The idea in one minute
  2. Step 1: get registered
  3. Step 2: put these values in your app
  4. The rules
  5. What happens, request by request
  6. The ID token: who the person is
  7. Keeping people signed in
  8. Using a library (recommended)
  9. The sign-in button
  10. Errors and what they mean
  11. Lifetimes and limits
  12. When access is taken away
  13. Checklist before going live
  14. Try it without writing code

The idea in one minute

Your app never sees a password. Instead:

  1. Your app sends the person's browser to TatvaOS.
  2. TatvaOS signs them in (if they are not already) and asks once: "may this app receive your name, work email and organisation?"
  3. TatvaOS sends the browser back to your app with a one-time code.
  4. Your server swaps the code for tokens. One of them, the ID token, says who the person is.

If the organisation later removes or suspends the person, or removes your app, their tokens stop working. That is the whole point: one account, managed in one place.

Step 1: get registered

An administrator of the organisation registers your app in TatvaOS: Organisation → Applications → New application. Give them three things.

They needGive them
NameWhat people see on the consent screen. For example Payroll.
Redirect URIThe exact https:// address in your app where TatvaOS sends people back (your callback). It must match character for character: no wildcards, and a trailing slash counts. For a developer's own machine, http://localhost… is allowed.
Server app or not?Yes if your app has a server that can keep a secret (websites, backends). No if it runs only on a phone or in a browser.

They get back a client id (starts with tos_) and, for a server app, a client secret (starts with toss_). The secret is shown only once. If it is lost, the app must be registered again.

Step 2: put these values in your app

Most libraries and frameworks have a settings page or config for exactly these.

SettingValue
Issuer (also called provider or authority)https://core.tatvaos.com
Discovery URLhttps://core.tatvaos.com/.well-known/openid-configuration
Client idfrom the administrator
Client secretfrom the administrator (server apps only)
Scopesopenid profile email, plus offline_access for a refresh token — server apps only (see below)
Response typecode
PKCEon, method S256

Your library reads the endpoints from the discovery URL. For reference, they are:

EndpointURL
Authorize (send the browser here)https://core.tatvaos.com/oauth/authorize
Token (your server calls this)https://core.tatvaos.com/api/oauth/token
Userinfohttps://core.tatvaos.com/api/oauth/userinfo
Keys (JWKS)https://core.tatvaos.com/api/oauth/jwks
Introspectionhttps://core.tatvaos.com/api/oauth/introspect
Revocationhttps://core.tatvaos.com/api/oauth/revoke

The rules

What happens, request by request

You only need this if you are not using a library, or want to understand what the library does.

1. Send the person to TatvaOS

Make a random code_verifier (43 to 128 characters). Compute code_challenge = base64url(sha256(code_verifier)). Make a random state and nonce. Keep all three in the person's session. Then redirect the browser to:

https://core.tatvaos.com/oauth/authorize
  ?response_type=code
  &client_id=tos_…
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback
  &scope=openid%20profile%20email%20offline_access
  &state=…
  &nonce=…
  &code_challenge=…
  &code_challenge_method=S256

The person signs in to TatvaOS if needed. The first time, they see a consent screen naming your app. After that, TatvaOS remembers.

2. Get the code back

https://app.example.com/auth/callback?code=…&state=…&iss=https%3A%2F%2Fcore.tatvaos.com%2F

Check state matches what you stored, and iss is https://core.tatvaos.com/. If the person cancelled or is not allowed, you get error=access_denied instead. The code works once and only for 60 seconds, so do the next step right away.

3. Swap the code for tokens (from your server)

curl -X POST https://core.tatvaos.com/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=… \
  -d redirect_uri=https://app.example.com/auth/callback \
  -d client_id=tos_… \
  -d client_secret=toss_… \
  -d code_verifier=…

A phone or browser app leaves out client_secret. The secret may also go in an HTTP Basic header as client_id:client_secret. You get back:

{
  "access_token":  "…",
  "token_type":    "Bearer",
  "expires_in":    600,
  "id_token":      "eyJhbGciOiJSUzI1NiIsImtpZCI6…",
  "refresh_token": "…",
  "scope":         "openid profile email offline_access"
}

refresh_token appears only if you asked for offline_access. The access and refresh tokens are opaque strings; do not try to read them. The ID token is explained next.

4. Find out who the person is

Read it from the ID token, or ask userinfo with the access token:

curl https://core.tatvaos.com/api/oauth/userinfo -H "Authorization: Bearer …"

{ "sub": "…", "tid": "…", "name": "Asha Rao", "email": "asha@example.com", "email_verified": true }

Userinfo checks every time. It answers 401 the moment the person is suspended, removed, or your app is revoked.

The ID token: who the person is

The ID token is a signed JWT (RS256). Your library verifies it against the keys at the JWKS URL, picking the key whose kid matches the token. Then read these claims:

ClaimMeaning
subThe person's permanent id. Use this as your key for the person. Never the email; emails change.
tidThe organisation's id. Same for everyone in that organisation.
email, email_verifiedWork address (with email scope). Verified means the organisation has proved it owns that domain.
nameDisplay name (with profile scope).
issMust be exactly https://core.tatvaos.com/.
audMust be your client id.
nonceMust match the one you sent.
expThe ID token lives 5 minutes. Use it to start the person's session in your app, then rely on your own session.
Keys rotate every 90 days, and a retired key stays published for one more day. Fetch keys by kid and fetch again when you meet an unknown one. Never hard-code a key.

Keeping people signed in

Server applications only. A phone or browser application cannot be given offline_access: TatvaOS refuses it when the application is registered, and refuses it again if it is asked for at sign-in. A refresh token grants weeks of access while nobody is present, and anything running in a browser cannot keep one safe — any cross-site scripting flaw in your own page would hand it over. Use short access tokens and send the person through TatvaOS again when one expires; if they still have a TatvaOS session, they will not be asked anything.

Access tokens last 10 minutes. With offline_access you also get a refresh token. When the access token expires, swap the refresh token for a new set:

curl -X POST https://core.tatvaos.com/api/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=… \
  -d client_id=tos_… \
  -d client_secret=toss_…

Three things to know:

Using a library (recommended)

Do not write the requests above yourself. Give your framework's OpenID Connect client the issuer, client id, secret and scopes, and turn PKCE on. Three examples of the shape:

ASP.NET Core

builder.Services.AddAuthentication(o => { o.DefaultScheme = "Cookies"; o.DefaultChallengeScheme = "oidc"; })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", o => {
        o.Authority = "https://core.tatvaos.com";
        o.ClientId = "tos_…";
        o.ClientSecret = builder.Configuration["TatvaOS:ClientSecret"]; // never in code
        o.ResponseType = "code";
        o.UsePkce = true;
        o.Scope.Clear();
        foreach (var s in new[] { "openid", "profile", "email", "offline_access" }) o.Scope.Add(s);
        o.CallbackPath = "/auth/callback";   // register https://your-app/auth/callback
        o.SaveTokens = true;
    });

Node.js (openid-client)

import * as client from 'openid-client';

const config = await client.discovery(new URL('https://core.tatvaos.com'), 'tos_…', process.env.TATVAOS_CLIENT_SECRET);

// send the person to TatvaOS
const codeVerifier = client.randomPKCECodeVerifier();
const url = client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://app.example.com/auth/callback',
  scope: 'openid profile email offline_access',
  code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
  code_challenge_method: 'S256',
  state, nonce,
});

// in the callback
const tokens = await client.authorizationCodeGrant(config, new URL(req.url, 'https://app.example.com'), {
  pkceCodeVerifier: codeVerifier, expectedState: state, expectedNonce: nonce,
});
const who = tokens.claims();   // who.sub, who.email, who.name, who.tid

Python (Authlib with Flask)

oauth = OAuth(app)
oauth.register(
    name='tatvaos',
    server_metadata_url='https://core.tatvaos.com/.well-known/openid-configuration',
    client_id='tos_…',
    client_secret=os.environ['TATVAOS_CLIENT_SECRET'],
    client_kwargs={'scope': 'openid profile email offline_access', 'code_challenge_method': 'S256'},
)
# send:     return oauth.tatvaos.authorize_redirect(url_for('callback', _external=True))
# callback: token = oauth.tatvaos.authorize_access_token(); who = token['userinfo']

The sign-in button

Use the same button everyone else uses, so a person recognises what it does before they click it. It is self-contained: no font to load, no image to host, no stylesheet to link. Point href at your own endpoint that starts the flow.

This is the button, at the size it should be:

Sign in with TatvaOS

Copy this. A dark-background version and an icon-only version for tight spaces are on the button assets page.

<style>
.tos-btn{display:inline-flex;align-items:center;gap:12px;height:44px;
  padding:0 20px 0 16px;border-radius:8px;border:1px solid #D9DEE8;
  background:#fff;color:#0B1B3F;text-decoration:none;cursor:pointer;
  font:600 15px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,
  Helvetica,Arial,sans-serif;letter-spacing:-.01em}
.tos-btn:hover{background:#F4F7FC;border-color:#C3CBDA}
.tos-btn:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(15,124,245,.35)}
.tos-btn svg{flex:none;width:20px;height:20px}
</style>

<a class="tos-btn" href="/auth/tatvaos/start">
  <svg viewBox="0 0 48 48" aria-hidden="true">
    <defs><linearGradient id="tosMark" x1="6" y1="4" x2="42" y2="44"
      gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#1E52D9"/>
      <stop offset=".55" stop-color="#2A7BEE"/>
      <stop offset="1" stop-color="#3BA9F7"/>
    </linearGradient></defs>
    <path fill="url(#tosMark)" d="M17.94 3.5 Q24 0 30.06 3.5 L38.72 8.5
      Q44.78 12 44.78 19 L44.78 29 Q44.78 36 38.72 39.5 L30.06 44.5
      Q24 48 17.94 44.5 L9.28 39.5 Q3.22 36 3.22 29 L3.22 19
      Q3.22 12 9.28 8.5 Z"/>
    <rect x="13" y="13" width="22" height="6.5" rx="2" fill="#fff"/>
    <rect x="20.75" y="13" width="6.5" height="23" rx="2" fill="#fff"/>
  </svg>
  Sign in with TatvaOS
</a>

Rules for using it

These exist so a person seeing the button knows what it does and can trust it.

Tell the people who click it one thing. Signing in with TatvaOS means their sign-in to your application depends on TatvaOS being reachable. If TatvaOS is down, they cannot sign in to you either. This is true of every identity provider, and it is worth saying plainly in your own help pages rather than leaving people to discover it during an outage.

Errors and what they mean

ErrorWhereMeaning
invalid_request "client_id is invalid"authorizeUnknown client id, or the app was revoked. Shown on a TatvaOS page; the browser does not come back to you.
invalid_request about the redirect URIauthorizeThe redirect URI does not exactly match a registered one.
invalid_request about code_challengeauthorizePKCE missing, or not S256.
access_deniedyour callbackThe person cancelled, belongs to another organisation, or cannot sign in.
login_required, consent_requiredyour callbackYou sent prompt=none and the person is not signed in or has not consented.
invalid_clienttokenWrong secret, unknown client id, or the app was revoked.
invalid_granttokenThe code expired, was already used, or the verifier is wrong. Or the refresh token was retired, reused, or its person or organisation is no longer active.
temporarily_unavailable (HTTP 429)token, authorizeToo many requests from your address in one minute. Wait a minute; do not retry in a loop.
HTTP 401userinfoThe access token expired or was revoked. Refresh, or sign the person in again.

Lifetimes and limits

ItemValue
Code60 seconds, single use
ID token5 minutes
Access token10 minutes, checked on every call
Refresh token14 days, replaced on every use
Token endpoint120 requests a minute per address
Authorize endpoint60 requests a minute per address

When access is taken away

What TatvaOS cannot undo. An ID token already delivered stays valid until it expires (that is why it lives five minutes). And your app's own session, the cookie you set after sign-in, is yours: TatvaOS cannot end it. People already signed in to your app stay signed in there until your app signs them out. If you need to react quickly, keep your own sessions short, or call userinfo when it matters.
Browser-only apps. An app that runs entirely in the browser, with no server, would need the token endpoint to accept cross-origin calls. TatvaOS does not offer that today. Give such an app a small backend to do the token exchange.

Checklist before going live

Try it without writing code

  1. Ask the administrator to register a test app with redirect URI https://oidcdebugger.com/debug and "Server application" unticked. Get the client id.
  2. Go to oidcdebugger.com. Authorize URI: https://core.tatvaos.com/oauth/authorize. Client ID: yours. Scope: openid profile email. Response type: code. Tick Use PKCE. Send request.
  3. You see the TatvaOS consent screen, then a page showing the code and your state. That is the sign-in leg working. The code-for-tokens leg is your server's part, which the debugger does not do.
  4. Ask the administrator to revoke the test app when you are done.