npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xrpl-commons/auth-sdk

v0.13.0

Published

XRP Account SDK for TypeScript apps (Nuxt, Vue, React, Next.js)

Downloads

1,104

Readme

@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": "[email protected]",
    "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.