@xrpl-commons/auth-sdk

XRP Account SDK for TypeScript apps (Nuxt, Vue, React, Next.js)
3,426
Install
npm install @xrpl-commons/auth-sdk
Latest Version:0.13.0
Vue:3
License:MIT
Last Updated:Jun 9, 2026
Links: npm
Maintainer: xrpl-commons-admin

@xrpl-commons/auth-sdk

SDK for integrating with the XRPL Commons Auth and Account platform.

  • Auth: OAuth 2.0 / OIDC on https://identity.xrpl.in
  • Account: shared profile and app data on https://profile.xrpl.in

This package supports both:

  • consumer apps that want XRP login and Account reads
  • downstream apps that want to integrate their own app-owned data into Account

Quick integration steps

Consumer app

  1. Register an OAuth client in XRP Profile admin with your exact callback URL.
  2. Configure your app with:
    • authBaseUrl: https://identity.xrpl.in
    • accountBaseUrl: https://profile.xrpl.in
    • your clientId
    • your exact redirect URI
  3. Start login with PKCE and request only the app read scopes you need.
  4. Exchange the authorization code for tokens.
  5. Call POST /account/user-info through AccountClient to read Account plus app-owned data.

Downstream app

  1. Keep your own business logic, role gating, and schema ownership.
  2. Implement POST /api/account/user-info.
  3. Validate the forwarded bearer token and resolve the user from XRP Identity.
  4. Return only your app-owned data fields or a standardized relay error.
  5. Register the app's relay URL on the linked OAuth client in XRP Profile admin.

Install

bun add @xrpl-commons/auth-sdk

Or:

npm install @xrpl-commons/auth-sdk

What Account owns

Account is the single consumer-facing integration point.

Consumers call:

POST /account/user-info

Account owns:

  • auth and scope enforcement
  • the outer request and response envelope
  • fan-out to downstream apps
  • partial success and per-app error handling

Downstream apps own:

  • their own field list
  • their own data semantics
  • their own role gating
  • their own /api/account/user-info implementation

Core clients

import { createAuthClient, createAccountClient } from '@xrpl-commons/auth-sdk'

const auth = createAuthClient({
  authBaseUrl: 'https://identity.xrpl.in',
  clientId: 'my-client-id',
  redirectUri: 'https://my-app.example.com/api/auth/xrpl-auth/callback',
  defaultScopes: ['openid', 'email', 'profile']
})

const account = createAccountClient({
  accountBaseUrl: 'https://profile.xrpl.in'
})

Auth usage

const authorizationUrl = auth.getAuthorizationUrl({
  state: '/dashboard',
  apps: ['learn', 'glow'],
  codeChallenge: '<pkce-code-challenge>',
  codeChallengeMethod: 'S256'
})

const userinfo = await auth.getUserinfo('<access-token>')

Server-side code exchange:

import { createServerAuthClient } from '@xrpl-commons/auth-sdk/server'

const serverAuth = createServerAuthClient({
  authBaseUrl: 'https://identity.xrpl.in',
  clientId: 'my-client-id',
  clientSecret: 'my-client-secret',
  redirectUri: 'https://my-app.example.com/api/auth/xrpl-auth/callback',
  defaultScopes: ['openid', 'email', 'profile']
})

Browser PKCE helpers

For pure browser apps or frontend-managed PKCE flows:

import {
  beginBrowserPkceLogin,
  completeBrowserPkceLogin,
  createAuthClient
} from '@xrpl-commons/auth-sdk'

const auth = createAuthClient({
  authBaseUrl: 'https://identity.xrpl.in',
  clientId: 'my-public-client-id',
  redirectUri: 'https://project-x.example.com/callback',
  defaultScopes: ['openid', 'email', 'profile']
})

await beginBrowserPkceLogin(auth, {
  returnUrl: '/dashboard',
  apps: ['learn', 'glow', 'hackathon']
})

On callback:

const params = new URLSearchParams(window.location.search)

const result = await completeBrowserPkceLogin(auth, {
  code: params.get('code')!,
  state: params.get('state')!
})

result.tokens.access_token
result.userinfo
result.session.returnUrl

Session helpers:

import { loadBrowserAuthSession, clearBrowserAuthSession } from '@xrpl-commons/auth-sdk'

Account user-info v1

Canonical consumer endpoint:

POST /account/user-info

Request shape:

const response = await account.getUserInfo(
  {
    apps: {
      items: [
        { appId: 'learn', fields: ['summary', 'progress'] },
        { appId: 'glow', fields: ['profile', 'roles', 'cohorts', 'judging'] },
        { appId: 'hackathon', fields: ['roles', 'teams', 'projects'] }
      ],
      limit: 25
    }
  },
  { accessToken: '<access-token>' }
)

Canonical request payload:

{
  "apps": {
    "items": [
      { "appId": "learn", "fields": ["summary", "progress"] },
      { "appId": "glow", "fields": ["profile", "roles", "cohorts", "judging"] },
      { "appId": "hackathon", "fields": ["roles", "teams", "projects"] }
    ],
    "limit": 25
  }
}

Continuation request:

const nextPage = await account.getUserInfo(
  {
    apps: {
      cursor: response.meta.nextAppsCursor
    }
  },
  { accessToken: '<access-token>' }
)

Response:

response.account
response.apps
response.meta.contract // 'account.user-info.v1'
response.meta.requestedAppCount
response.meta.returnedAppCount
response.meta.nextAppsCursor

Canonical response payload:

{
  "account": {
    "id": "69b1bca1763be50a6457b595",
    "email": "admin@xrpl-commons.org",
    "emailVerified": true,
    "status": "active"
  },
  "apps": [
    {
      "appId": "learn",
      "label": "Learn",
      "scope": "learn:read",
      "access": { "mode": "scope" },
      "data": {
        "summary": {
          "completedLessons": 1,
          "currentStreak": 1,
          "totalXp": 100
        },
        "progress": [
          {
            "resourceId": "lesson-blockchain-and-crypto-basics-what-is-a-blockchain",
            "status": "completed",
            "updatedAt": "2026-01-01T00:04:00.000Z"
          }
        ]
      }
    },
    {
      "appId": "hackathon",
      "label": "Hackathon",
      "scope": "hackathon:read",
      "access": { "mode": "scope" },
      "error": {
        "code": "user_not_linked",
        "message": "This XRP Identity account is not linked to a Hackathon user."
      }
    }
  ],
  "meta": {
    "contract": "account.user-info.v1",
    "requestedAppCount": 2,
    "returnedAppCount": 2,
    "nextAppsCursor": null
  }
}

App entries can return either data or error:

for (const app of response.apps) {
  if (app.error) {
    console.error(app.appId, app.error.code, app.error.message)
    continue
  }

  console.log(app.appId, app.data)
}

App request fields

These are example apps Identity currently hosts and their published fields — they are not enforced or defined by the SDK. appId and fields are plain strings; any app Identity exposes works without an SDK change. Field lists are owned by each app (discoverable via Identity), not shipped here.

Current app-owned public fields:

Learn

['summary', 'progress']

Glow

['profile', 'roles', 'cohorts', 'nominations', 'judging', 'grants', 'admin']

Hackathon

['roles', 'hackathons', 'teams', 'projects', 'judging', 'payouts', 'admin']

These inner payloads are app-owned. Account does not define their business semantics.

Helper utilities

import {
  appReadScope,
  buildAppReadScopes,
  mergeScopes
} from '@xrpl-commons/auth-sdk'

const scopes = mergeScopes(
  ['openid', 'email', 'profile'],
  buildAppReadScopes(['learn', 'glow'])
)

App access

App data is opaque to the SDK — appId is any string and data defaults to Record<string, unknown>. Apps are discovered/relayed by Identity; the SDK does not hardcode which apps exist. Supply your own type when you know an app's shape:

import { findAppData } from '@xrpl-commons/auth-sdk'

interface GlowData {
  roles?: Record<string, boolean>
  cohorts?: { id: string; name: string }[]
}

// works for built-in apps and ad-hoc ones alike (e.g. 'regul8')
const glow = findAppData<GlowData>(response, 'glow')

if (glow?.data) {
  glow.data.roles
  glow.data.cohorts
}

Nuxt module

export default defineNuxtConfig({
  modules: ['@xrpl-commons/auth-sdk'],
  xrplAuth: {
    authBaseUrl: 'https://identity.xrpl.in',
    accountBaseUrl: 'https://profile.xrpl.in',
    clientId: 'my-client-id',
    redirectUri: 'https://my-app.example.com/api/auth/xrpl-auth/callback',
    defaultScopes: ['openid', 'email', 'profile']
  }
})
const { login } = useXRPLAuth()
await login({ returnUrl: '/progress', apps: ['learn'] })
const { getUserInfo, getAppData, findAppData, hasAppData } = useXRPLAccount()
// pass the fields you want; supply a type for the returned data if you have one
const learn = await getAppData('learn', { fields: ['summary', 'progress'] })

Downstream app integration contract

If your app wants to expose app-owned data through XRP Account, implement:

POST /api/account/user-info

Account calls it with:

Authorization: Bearer <user-access-token>
Content-Type: application/json

Request body:

{
  "contract": "account.app-user-info.v1",
  "appId": "glow",
  "fields": ["profile", "roles", "cohorts"],
  "context": {
    "accountId": "507f1f77bcf86cd799439011"
  }
}

Minimal implementation steps:

  1. Accept only POST.
  2. Require Authorization: Bearer <user-access-token>.
  3. Validate:
    • contract === "account.app-user-info.v1"
    • your appId
    • requested fields
  4. Resolve the user from the bearer token, not from the request body alone.
  5. Build only the role-eligible fields for that user.
  6. Omit role-ineligible sections entirely.
  7. Return either a data object or an error object.

Rules:

  • contract must be account.app-user-info.v1
  • appId must match your app
  • fields must be validated against your app contract
  • the bearer token is the source of user identity
  • role-ineligible fields must be omitted entirely

Success response:

{
  "contract": "account.app-user-info.v1",
  "appId": "glow",
  "data": {
    "profile": {},
    "roles": {},
    "cohorts": []
  },
  "meta": {
    "servedAt": "2026-05-08T12:00:00.000Z"
  }
}

Error response:

{
  "contract": "account.app-user-info.v1",
  "appId": "glow",
  "error": {
    "code": "forbidden_field_access",
    "message": "Requested fields are not available for this user role."
  }
}

Supported downstream error codes:

type RelayErrorCode =
  | 'invalid_token'
  | 'insufficient_scope'
  | 'invalid_request'
  | 'unknown_field'
  | 'forbidden_field_access'
  | 'user_not_linked'
  | 'data_unavailable'
  | 'temporarily_unavailable'
  | 'internal_error'

Architecture notes

  • PKCE with S256 is mandatory for public browser flows.
  • Client secrets must never be used in browser code.
  • Account is the only consumer-facing aggregator.
  • Learn, Glow, Hackathon, and future apps own their own field visibility and role gating.
  • Account may return partial success, so callers must handle per-app error entries.

Related Packages

@asgardeo/nuxt

Nuxt module for Asgardeo - Authentication and Identity Management

3,712 18
@mbsoftlabs/nuxt-oidc-auth

Nuxt 4 module for OIDC/SSO authentication with Laravel Passport or any OIDC prov...

4,296 0
@wocha/nuxt

Nuxt 3 module for Wocha authentication (BFF pattern with httpOnly cookies)

599
@drmhse/authos-cli

Pre-1.0 CLI for scaffolding AuthOS integrations

3,866 6
cauthy-auth

Sign in with Cauthy: one route to mount, getSession on the server, useSession in...

2,148

Version History

Version Released Vue License
0.13.0 ^3.5.0 MIT
0.12.0 ^3.5.0 MIT
0.11.0 ^3.5.0 MIT
0.10.0 ^3.5.0 MIT
0.9.0 ^3.5.0 MIT
Pre-releases (2)
Version Released Vue License
0.12.0-beta.2 ^3.5.0 MIT
0.12.0-beta.1 ^3.5.0 MIT