@kiriminaja/auth
v0.0.5
Published
Browser OAuth PKCE SDK for Kiriminaja
Downloads
569
Readme
@kiriminaja/auth
Browser-only, ESM-only OAuth 2.0 authorization-code + PKCE helper for Kiriminaja.
It opens https://app.kiriminaja.com/oauth/authorize/ in a popup when possible and automatically falls back to a full-page redirect when the popup is blocked, closed, or times out.
Install
bun add @kiriminaja/authOAuth client registration (alpha)
Kiriminaja OAuth is currently in alpha. Before integrating, register your application with the Kiriminaja team by emailing [email protected].
Include the following details in your request:
- Application name and a short description of its intended use.
- Environment (
development,staging, orproduction). - Exact HTTPS callback URL(s), such as
https://your-app.example/auth/callback. - Requested OAuth scope(s).
- A technical contact for the integration.
After approval, Kiriminaja provides the clientId, approved scopes,
authorization endpoint, token endpoint, and token-validation details for the
requested environment. Callback URLs must match the registered URL exactly;
do not use wildcard URLs or derive them from user input.
Configure the client
The SDK is browser-only. In Nuxt or another SSR framework, import and initialize
it only in client-side code (for example, a .client plugin or behind
if (import.meta.client)). It deliberately throws if Nuxt loads it through an
import.meta.server bundle or if it is constructed outside a browser.
import { KiriminajaAuth } from '@kiriminaja/auth'
export const auth = new KiriminajaAuth({
clientId: 'sandbox-dev-sso',
scope: 'sandbox',
redirectUri: 'https://example.com/auth/callback',
})Use a development authorization server
The production authorization endpoint defaults to
https://app.kiriminaja.com/oauth/authorize/. Override authorizationEndpoint
when your application is running against an approved development environment:
export const auth = new KiriminajaAuth({
clientId: 'sandbox-dev-sso',
scope: 'sandbox',
redirectUri: 'https://example.com/auth/callback',
authorizationEndpoint: 'https://app.dev.kiriminaja.com/oauth/authorize/',
})Use an endpoint controlled by Kiriminaja and ensure its OAuth client has the same registered callback URL. Do not accept this value from untrusted input.
Complete integration guide
Register one exact HTTPS callback URL with Kiriminaja, for example
https://your-app.example/auth/callback. That page is required for both
popup and full-page redirect authorization.
Your integration has four responsibilities:
- Create one
KiriminajaAuthinstance in browser-only code. - Start authorization from a user gesture (such as a Sign in button).
- Handle the registered callback route using
notifyPopupCallback()andhandleRedirectCallback(). - Send the resulting code and PKCE verifier to your backend for exchange.
1. Start sign-in from your login page
Call authorize() from the click handler. It defaults to popup mode and opens
the Kiriminaja authorization page centered on screen.
async function exchange(result: { code: string; codeVerifier: string }) {
const response = await fetch('/api/auth/kiriminaja/exchange', {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify(result),
})
if (!response.ok) throw new Error('Unable to complete sign-in.')
}
async function signIn(): Promise<void> {
// Must be called directly from a user interaction, otherwise browsers may
// block the popup.
const result = await auth.authorize()
// A successful popup resolves here. Exchange the code, then update your
// application session/profile state and navigate to the signed-in page.
if (result) {
await exchange(result)
window.location.replace('/dashboard')
}
// No result means the SDK navigated this tab to the authorization endpoint
// as its redirect fallback. The callback page completes that flow.
}The SDK automatically falls back to a full-page redirect when the popup is blocked, manually closed, or times out. To always use a redirect, call:
await auth.authorize({ mode: 'redirect' })2. Implement the callback page
At the exact path configured as redirectUri, use this client-side callback
handler. Do not process OAuth query parameters on the server.
import { notifyPopupCallback } from '@kiriminaja/auth'
import { auth } from './auth'
async function exchange(result: { code: string; codeVerifier: string }) {
const response = await fetch('/api/auth/kiriminaja/exchange', {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify(result),
})
if (!response.ok) throw new Error('Unable to complete sign-in.')
}
async function completeCallback(): Promise<void> {
// Popup flow: post the complete callback URL to the login window and close
// this window. The login page's authorize() promise then resolves.
if (notifyPopupCallback()) return
// Redirect flow: this tab is the original application tab. Validate state,
// exchange its authorization code, then load the authenticated application.
const result = auth.handleRedirectCallback()
await exchange(result)
window.location.replace('/dashboard')
}
void completeCallback()notifyPopupCallback() returns true only when the callback page has an
opener. It sends kiriminaja:oauth:callback only to the callback URL's origin,
then closes the popup. When it returns false, the callback is a normal
redirect. The SDK validates the message origin, callback origin, and OAuth
state before returning a popup result.
Flow summary
| Situation | What authorize() does | Where code exchange happens |
| --- | --- | --- |
| Popup opens and user succeeds | Resolves with AuthorizationResult in the login window | Login-page handler |
| Popup is blocked, closed, or times out | Navigates the current tab to authorization | Callback page |
| mode: 'redirect' | Navigates the current tab to authorization | Callback page |
| User denies access | Returns OAuth error on the callback URL | Handle/display error on callback page |
For popup success, update your reactive session/profile store after exchange.
If your backend sets an HTTP-only session cookie, a full navigation (such as
window.location.replace('/dashboard')) is the most reliable way to start the
application with the new authenticated session.
Exchange and validate on your backend
This package deliberately does not exchange authorization codes or validate access tokens. It is a browser SDK, so it must never receive an OAuth client secret or persist tokens that grant access to your backend.
Create an endpoint in your application backend (for example,
POST /api/auth/kiriminaja/exchange) and send it the code and
codeVerifier returned by this SDK. That endpoint must:
- Exchange the authorization code with the Kiriminaja OAuth token endpoint configured for your OAuth client, using the client credentials only on the server.
- Validate the returned token according to the token format and issuer contract supplied by Kiriminaja (signature, issuer, audience, expiry, and required scopes).
- Establish your application's server-side session or issue its own secure session cookie. Do not return a confidential-client refresh token to the browser.
The authorization endpoint (/oauth/authorize/) is intentionally separate
from the token endpoint. Obtain the correct token endpoint and validation/JWKS
details for each environment from Kiriminaja's OAuth configuration; do not
derive or hard-code them from the authorization URL.
If you need a reusable server implementation, provide it as a separate
server-side package. Keeping it out of @kiriminaja/auth preserves this
package's browser-only API and prevents accidental exposure of secrets.
Generated authorization URL
The SDK generates an authorization request equivalent to:
https://app.kiriminaja.com/oauth/authorize/?client_id=sandbox-dev-sso&scope=sandbox&redirect_uri=https%3A%2F%2Fyour-app.example%2Fauth%2Fcallback&state=<random>&code_challenge=<sha256-pkce-challenge>&code_challenge_method=S256Use await auth.createAuthorizationUrl() if you only need the URL, for example to render a login link. It stores the matching PKCE verifier in sessionStorage so handleRedirectCallback() can validate it later.
Security model
- A random
stateand PKCE code verifier are created per authorization request. - The verifier is stored in
sessionStorageand consumed once after the callback. - Exchange the code for tokens from your backend. Do not expose a confidential OAuth client secret in browser code.
- Requires a secure browser context with Web Crypto support.
Publishing
The package export map exposes only an import entry—there is no CommonJS require export.
Releases are created from an up-to-date, clean main checkout. The command validates the package, bumps its version, creates a vX.Y.Z Git tag, and pushes it. The tag-triggered GitHub Actions workflow publishes the package.
bun run release # patch release
bun run release minor # minor release
bun run release major # major releasePublishing uses npm Trusted Publishing through GitHub Actions; no npm token is
required. Configure npm's trusted publisher with this repository and the
.github/workflows/release.yml workflow.
