Developers

Sign in with Oshaani

Let people sign in to your website or app with their oshaani.com account. oshaani.com is a standard OpenID Connect provider, so any OIDC library works. You get verified profile details, centrally managed roles and permissions, and single logout across every connected site.

Overview

Sites that already use Sign in with Oshaani include social.oshaani.com. The integration is the same for every site:

1 · RedirectYour site sends the visitor to oshaani.com to sign in.
2 · Sign inThey sign in on oshaani.com (password, Google or LinkedIn). Already signed in? No prompt.
3 · Callbackoshaani.com redirects back to you with a one-time code.
4 · ExchangeYour server swaps the code for tokens, including a signed ID token.
5 · SessionYou verify the ID token and sign the user in to your site.

What you get:

  • One account everywhere — users don't create a new password for your site.
  • Verified email — the email_verified claim tells you whether oshaani.com has confirmed the address.
  • Central authorization — roles and permissions are managed once in oshaani.com and sent to your site at every sign-in.
  • Single logout — when a user signs out of oshaani.com, or loses access, your site is told immediately.

Endpoints

Most libraries only need the issuer or the discovery URL and will find everything else automatically.

Issuerhttps://oshaani.com/o
Must match the iss claim exactly — no trailing slash.
Discovery documenthttps://oshaani.com/o/.well-known/openid-configuration
Authorization endpointhttps://oshaani.com/o/authorize/
Token endpointhttps://oshaani.com/o/token/
UserInfo endpointhttps://oshaani.com/o/userinfo/
JWKS (signing keys)https://oshaani.com/o/.well-known/jwks.json
End session (logout)https://oshaani.com/o/logout/
Token revocationhttps://oshaani.com/o/revoke_token/
FlowAuthorization code (response_type=code) only
PKCERequired on every request. Use code_challenge_method=S256.
Client authenticationclient_secret_basic (HTTP Basic) or client_secret_post
ID token signingRS256, keys published at the JWKS URL
Scopesopenid (sign you in)
profile (your name and username)
email (your email address)
roles (your oshaani roles and permissions)
LifetimesAuthorization code 60 s · access token 3600 s · ID token 3600 s · refresh tokens rotate on every use

1. Register your site

Clients are registered by the Oshaani team — there is no self-service sign-up, so every connected site is known and reviewed. Email support@oshaani.com with:

DetailExample
Site name (shown to admins)Acme Helpdesk
Redirect URI(s) — where oshaani.com sends users after sign-in. Exact match, HTTPS only.https://help.acme.com/auth/oshaani/callback
Post-logout redirect URI(s) — where users land after signing out. Optional.https://help.acme.com/
Back-channel logout URI — receives logout notifications. Optional but recommended.https://help.acme.com/auth/oshaani/backchannel-logout
Who may sign inAny oshaani.com user, or only users who have been given a role for your site
App typeServer-side web app (gets a client secret), or single-page / mobile app (public client, PKCE only)

You'll receive a client_id and, for server-side apps, a client_secret. The secret is shown once — store it in your secrets manager or environment variables, never in source control or front-end code. If it leaks, ask us to rotate it; the old one stops working immediately.

2. Configure your app

Add the credentials to your app's environment:

.env
OSHAANI_ISSUER=https://oshaani.com/o
OSHAANI_CLIENT_ID=your-client-id
OSHAANI_CLIENT_SECRET=your-client-secret
OSHAANI_REDIRECT_URI=https://help.acme.com/auth/oshaani/callback

Then add a “Sign in with Oshaani” button that links to your login route (step 3). If your site already has its own accounts, decide how to connect them: we recommend matching existing users by email only when email_verified is true, and otherwise letting signed-in users link their Oshaani account from their profile.

3. The sign-in flow

3a. Send the user to oshaani.com

Generate a random state, nonce and PKCE code_verifier, store them in the user's session, and redirect to the authorization endpoint:

Redirect (line breaks added for readability)
GET https://oshaani.com/o/authorize/
    ?response_type=code
    &client_id=your-client-id
    &redirect_uri=https%3A%2F%2Fhelp.acme.com%2Fauth%2Foshaani%2Fcallback
    &scope=openid%20profile%20email%20roles
    &state=RANDOM_STATE
    &nonce=RANDOM_NONCE
    &code_challenge=BASE64URL(SHA256(code_verifier))
    &code_challenge_method=S256
  • redirect_uri must exactly match one you registered.
  • Ask only for the scopes you need. openid is always required; add roles if you use central authorization.
  • Optional: prompt=login forces the user to re-enter their password.

3b. Handle the callback

oshaani.com redirects back to your redirect_uri with ?code=…&state=…. Check that state equals the value you stored; if it doesn't, stop. If the user declined or something went wrong you'll get ?error=…&error_description=… instead.

3c. Exchange the code for tokens (server-side)

The code is single-use and expires after 60 seconds.

curl
curl -X POST https://oshaani.com/o/token/ \
  -u "$OSHAANI_CLIENT_ID:$OSHAANI_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="CODE_FROM_CALLBACK" \
  -d redirect_uri="$OSHAANI_REDIRECT_URI" \
  -d code_verifier="CODE_VERIFIER_FROM_SESSION"
Response
{
  "access_token": "…",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "openid profile email roles",
  "refresh_token": "…",
  "id_token": "eyJhbGciOiJSUzI1NiIs…"
}

Public clients (single-page and mobile apps) send client_id in the form body instead of a secret; PKCE protects the exchange.

4. Validate the ID token

The id_token is a JWT signed with RS256. Your OIDC library normally does this for you; if you validate it yourself, check all of the following before trusting it:

  1. The signature verifies with the key from the JWKS whose kid matches the token header, and alg is RS256.
  2. iss is exactly https://oshaani.com/o.
  3. aud contains your client_id.
  4. exp is in the future (allow a minute of clock skew at most).
  5. nonce equals the value you stored in step 3a.

Cache the JWKS, but re-fetch it when you see a kid you don't know — that's how key rotation reaches you.

Identify users by sub, not by email. sub is stable and unique for each oshaani.com user; email addresses can change.

Claims

Claims appear in the ID token and from the UserInfo endpoint (GET https://oshaani.com/o/userinfo/ with Authorization: Bearer <access_token>), filtered by the scopes you were granted.

ClaimScopeDescription
subopenidStable unique user ID (string). Use this as the key for the user in your database.
nameprofileFull name, or the username if no name is set.
given_name, family_nameprofileFirst and last name (may be empty).
preferred_usernameprofileoshaani.com username. Not guaranteed to stay the same.
emailemailPrimary email address.
email_verifiedemailtrue only if oshaani.com has confirmed the address (for example via Google or LinkedIn sign-in). Don't link existing accounts by email when this is false.
rolesrolesArray of role slugs granted to this user for your site, including global roles. Example: ["staff"].
permissionsrolesArray of permission strings from those roles. Example: ["tickets.view", "tickets.*"].
Example decoded ID token
{
  "iss": "https://oshaani.com/o",
  "sub": "1042",
  "aud": "your-client-id",
  "iat": 1791100000,
  "exp": 1791103600,
  "auth_time": 1791099950,
  "nonce": "RANDOM_NONCE",
  "name": "Ada Lovelace",
  "given_name": "Ada",
  "family_name": "Lovelace",
  "preferred_username": "ada",
  "email": "ada@example.com",
  "email_verified": true,
  "roles": ["staff"],
  "permissions": ["admin.access"]
}

Roles & permissions

Authorization is managed centrally on oshaani.com, so access is granted and revoked in one place for every connected site.

  • Global roles apply to every site. Two exist by default: admin (permission *) and staff (permission admin.access).
  • Site roles are created for one site only, with whatever permission strings that site understands — for example tickets.view or billing.*.
  • Wildcards: by convention * means every permission and app.* means every permission starting with app.. Your site decides how to interpret them.
  • Restricted sites: if you asked for “only users with a role”, people without one see an access-denied page on oshaani.com and are never sent back to you.
  • Freshness: roles are sent at every sign-in. When a role is removed or a user is deactivated, oshaani.com sends a back-channel logout so the user has to sign in again and picks up their new access.
Example: checking a permission (Python)
def has_permission(granted, wanted):
    for perm in granted:
        if perm == "*" or perm == wanted:
            return True
        if perm.endswith(".*") and wanted.startswith(perm[:-1]):
            return True
    return False

has_permission(claims["permissions"], "tickets.close")

Logout

Sign the user out everywhere (RP-initiated logout)

After clearing your own session, send the user to the end-session endpoint to sign them out of oshaani.com too:

Redirect
GET https://oshaani.com/o/logout/
    ?id_token_hint=ID_TOKEN_FROM_SIGN_IN
    &post_logout_redirect_uri=https%3A%2F%2Fhelp.acme.com%2F
    &client_id=your-client-id
    &state=OPTIONAL_STATE

post_logout_redirect_uri must be one you registered. Keep the ID token from sign-in in the session so you can send it as id_token_hint.

Get told when the user signs out elsewhere (back-channel logout)

If you registered a back-channel logout URI, oshaani.com sends a server-to-server POST to it when the user signs out of oshaani.com, has a role added or removed, or is deactivated. The body is form-encoded with a single field, logout_token, a signed JWT:

Decoded logout token
// header
{ "alg": "RS256", "typ": "logout+jwt", "kid": "…" }
// payload
{
  "iss": "https://oshaani.com/o",
  "aud": "your-client-id",
  "iat": 1791100000,
  "exp": 1791100120,
  "jti": "5c0e5f5e-…",
  "sub": "1042",
  "events": { "http://schemas.openid.net/event/backchannel-logout": {} }
}

To handle it:

  1. Verify the signature, iss, aud and exp exactly as for an ID token.
  2. Check that events contains http://schemas.openid.net/event/backchannel-logout and that there is no nonce claim.
  3. Reject a jti you have already seen (keep them for a few minutes).
  4. End every session on your site for that sub — the token has no sid.
  5. Respond 200 OK. Respond 400 if validation fails. The endpoint must not require a CSRF token or a session cookie.

Code examples

These use well-known OIDC libraries that handle discovery, PKCE, state, nonce and token validation for you.

Node.js — Express with openid-client 5.x

const { Issuer, generators } = require('openid-client');

const oshaani = await Issuer.discover('https://oshaani.com/o/.well-known/openid-configuration');
const client = new oshaani.Client({
  client_id: process.env.OSHAANI_CLIENT_ID,
  client_secret: process.env.OSHAANI_CLIENT_SECRET,
  redirect_uris: [process.env.OSHAANI_REDIRECT_URI],
  response_types: ['code'],
});

app.get('/auth/oshaani/login', (req, res) => {
  const code_verifier = generators.codeVerifier();
  const state = generators.state();
  const nonce = generators.nonce();
  req.session.oshaani = { code_verifier, state, nonce };
  res.redirect(client.authorizationUrl({
    scope: 'openid profile email roles',
    code_challenge: generators.codeChallenge(code_verifier),
    code_challenge_method: 'S256',
    state,
    nonce,
  }));
});

app.get('/auth/oshaani/callback', async (req, res) => {
  const { code_verifier, state, nonce } = req.session.oshaani || {};
  const tokenSet = await client.callback(
    process.env.OSHAANI_REDIRECT_URI,
    client.callbackParams(req),
    { code_verifier, state, nonce },
  );
  const claims = tokenSet.claims();          // validated ID token claims
  req.session.user = { id: claims.sub, email: claims.email, roles: claims.roles };
  req.session.idToken = tokenSet.id_token;   // keep for logout
  res.redirect('/');
});

Python — Flask with Authlib

from authlib.integrations.flask_client import OAuth

oauth = OAuth(app)
oauth.register(
    name="oshaani",
    server_metadata_url="https://oshaani.com/o/.well-known/openid-configuration",
    client_id=os.environ["OSHAANI_CLIENT_ID"],
    client_secret=os.environ["OSHAANI_CLIENT_SECRET"],
    client_kwargs={"scope": "openid profile email roles", "code_challenge_method": "S256"},
)

@app.route("/auth/oshaani/login")
def oshaani_login():
    return oauth.oshaani.authorize_redirect(os.environ["OSHAANI_REDIRECT_URI"])

@app.route("/auth/oshaani/callback")
def oshaani_callback():
    token = oauth.oshaani.authorize_access_token()   # checks state, nonce and the ID token
    claims = token["userinfo"]
    session["user"] = {"id": claims["sub"], "email": claims["email"], "roles": claims.get("roles", [])}
    session["id_token"] = token["id_token"]
    return redirect("/")

Next.js — Auth.js (NextAuth v5)

// auth.ts — callback URL to register: https://your-app.com/api/auth/callback/oshaani
import NextAuth from "next-auth";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    {
      id: "oshaani",
      name: "Oshaani",
      type: "oidc",
      issuer: "https://oshaani.com/o",
      clientId: process.env.AUTH_OSHAANI_ID,
      clientSecret: process.env.AUTH_OSHAANI_SECRET,
      authorization: { params: { scope: "openid profile email roles" } },
      checks: ["pkce", "state", "nonce"],
    },
  ],
});

Any other certified OpenID Connect client library works the same way: point it at the discovery URL, set the client ID, secret and redirect URI, and enable PKCE with S256.

Security checklist

  • Use HTTPS for every redirect, post-logout and back-channel URI.
  • Always send PKCE (S256), state and nonce, and check them on the way back.
  • Exchange codes and keep the client secret on your server only — never in browser JavaScript or a mobile app bundle.
  • Validate every ID token and logout token as described above; don't just decode them.
  • Key users by sub. Only trust email for account linking when email_verified is true.
  • Re-check permissions on the server for every protected action; don't rely on hiding buttons.
  • Store refresh tokens encrypted, and revoke them at https://oshaani.com/o/revoke_token/ when a user signs out.

Troubleshooting

What you seeLikely cause
Error page on oshaani.com mentioning the redirect URIThe redirect_uri isn't registered or differs slightly (http vs https, trailing slash, port). It must match exactly.
invalid_request about code_challengePKCE is missing. Every request needs code_challenge and code_challenge_method=S256.
invalid_client from the token endpointWrong client ID or secret, or the secret was rotated.
invalid_grant from the token endpointThe code expired (60 s), was already used, the redirect_uri differs from step 3a, or the code_verifier doesn't match the challenge.
Issuer mismatch in your libraryConfigure the issuer as exactly https://oshaani.com/o — no trailing slash, no www..
User sees “You don't have access to this site”Your site only allows users with a role and this user has none. Ask an Oshaani admin to grant one.
email_verified is falseThe user registered with an unconfirmed email. Ask them to sign in to oshaani.com with Google or LinkedIn once, or link accounts manually.
Roles look out of dateRoles are read at sign-in. Handle back-channel logout, or ask the user to sign in again.

Still stuck? Email support@oshaani.com with your client ID, the time of the attempt and the exact error. Never send your client secret.

Support