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

@padosoft/laravel-iam-react-native

v1.2.0

Published

Thin, fail-closed React Native client and hooks for the Laravel IAM control plane.

Readme

@padosoft/laravel-iam-react-native

Thin, fail-closed React Native client and hooks for the Laravel IAM control plane.

tests npm license docs

Ask the IAM server "can this user do this?" from your React Native (or React) app — with the exact same wire contract and guarantees as the PHP and Node clients, plus React hooks that stay fail-closed while loading.

📚 Full documentation: doc.laravel-iam-react-native.padosoft.com — quickstart, hook lifecycle, fail-closed theory, the Hermes/Web Crypto caveat, ADRs, and the full API reference.

Why

  • Fail-closed by construction. Network error, timeout, 5xx, 4xx, malformed body, or unverifiable token resolves to deny. Always. Loading resolves to deny. No fail-open switch.
  • No PDP logic client-side. Every verdict comes from the server's decisions/check. The client never interprets grants.
  • React Native safe. No node:crypto, no Node built-ins. Uses the RN fetch polyfill and jose (Web Crypto / globalThis.crypto.subtle).
  • Zero runtime coupling to the Node SDK. Wire types are imported from @padosoft/laravel-iam-node with import type — completely erased at build time. No Node SDK runtime code runs in your app.
  • First-class React hooks. usePermission and useCan integrate with React context — check a permission in one line.

Install

npm install @padosoft/laravel-iam-react-native

Requires React Native >= 0.71 (Hermes with globalThis.crypto.subtle) for verifyToken. For check() / hooks only, any RN version with fetch works.

Quick start

1. Configure the client and provider

import { IamClient, IamProvider } from '@padosoft/laravel-iam-react-native';

const iam = new IamClient({
  baseUrl: 'https://iam.example.com/api/iam/v1',
  token: process.env.IAM_SERVICE_TOKEN,
  timeoutMs: 2000,
  cache: { ttlMs: 5000 },
});

export default function App() {
  const userId = useAuth().userId;

  return (
    <IamProvider client={iam} subject={{ type: 'user', id: userId }}>
      <Navigation />
    </IamProvider>
  );
}

2. Check permissions with hooks

import { usePermission } from '@padosoft/laravel-iam-react-native';

function StockAdjustButton({ warehouseId }: { warehouseId: string }) {
  const { allowed, loading } = usePermission(
    'stock.adjust',
    { type: 'warehouse', id: warehouseId },
  );

  if (loading) return <ActivityIndicator />;
  if (!allowed) return null;
  return <Button title="Adjust stock" onPress={handleAdjust} />;
}

3. Or use the client imperatively

import { IamClient } from '@padosoft/laravel-iam-react-native';

const decision = await iam.check({
  subject: { type: 'user', id: 'usr_123' },
  application: 'warehouse',
  permission: 'stock.adjust',
  resource: { type: 'warehouse', id: 'wh_milan' },
  context: { amount: 300 },
});

if (!decision.allowed) throw new Error('Forbidden');
if (decision.requiresStepUp) promptStepUp(decision.requiredAal);

Fail-closed: read this

allowed === true alone is not permission. When requiresStepUp is true, the action is only permitted at a higher AAL. The hooks apply isGranted() automatically — they only set allowed: true when the PDP allowed and no step-up is pending.

Provider & Hooks API

<IamProvider client={...} subject={...}>

| Prop | Type | Description | |------|------|-------------| | client | IamClient | Pre-configured client instance. | | subject | Subject | The authenticated user. Hooks use it automatically. | | children | ReactNode | Your app tree. |

useIam()

Returns { client, subject } from the nearest IamProvider. Throws if no provider is found.

useCan(query: DecisionQuery): PermissionState

Full-control hook — accepts a complete DecisionQuery.

const { allowed, loading, requiresStepUp } = useCan({
  subject: { type: 'user', id: userId },
  permission: 'doc.publish',
  resource: { type: 'document', id: docId },
});

useDelegatedPermission(actors, permission, resource?, extra?): PermissionState

Like usePermission, but asks whether an agent may do it on behalf of the provider's subject. See Delegated access.

usePermission(permission, resource?, extra?): PermissionState

Convenience hook — reads subject from context, you supply permission and optionally resource.

const { allowed } = usePermission('orders.approve', { type: 'order', id: orderId });

PermissionState

| Field | Type | Description | |-------|------|-------------| | allowed | boolean | true only when PDP granted AND no step-up pending. false while loading. | | loading | boolean | true while the check is in flight. | | requiresStepUp | boolean | true if a higher AAL is required. |

Client API

new IamClient(config)

| Option | Default | Description | |--------|---------|-------------| | baseUrl | required | Full API base, e.g. https://iam.example.com/api/iam/v1. | | token | — | The user's access token, sent as Authorization: Bearer. | | timeoutMs | 2000 | Per-request timeout in ms. | | retries | 0 | Retries for idempotent network errors (never on 4xx/5xx). | | cache | off | { ttlMs, maxEntries? } in-memory decision cache. Delegated decisions bypass it by design. | | checkDelegatedPath | decisions/check-delegated | Path for the delegated PDP check. | | verify | — | { issuer?, audience?, jwksUri? } defaults for verifyToken. | | fetch | globalThis.fetch | Inject a custom fetch (tests, proxies). |

Credential model — this is a PUBLIC client (no shared secret)

Unlike the server SDKs (laravel-iam-client, laravel-iam-node, laravel-iam-rust), a mobile app is a public OAuth client: it must never embed a client_secret (anything shipped in an app binary is extractable). So the client-secret rotation / self-fetch feature does not apply here — there is no secret to rotate.

Instead, obtain the token through the Authorization Code + PKCE flow (the user logs in against IAM), keep it short-lived, and refresh it with the refresh token — never with a static secret. Pass the current user access token as token; when it expires, run the refresh/re-auth flow and update the client. See Application credentials & lifecycle (§ public clients).

check(query): Promise<Decision>

POST {baseUrl}/decisions/check. Returns a normalised Decision. Never throws.

can(query): Promise<boolean>

check() reduced to the fail-safe boolean.

listResources(subject, relation): Promise<Resource[]>

ReBAC list-resources. Returns [] on any error.

verifyToken(jwt, options?): Promise<Claims>

Verifies an ES256 token against the server JWKS. Rejects with TokenVerificationError on any failure — including a valid delegated token, see below.

Delegated access (agents acting for the user)

When an AI agent acts on behalf of a user, the token carries two identities: sub is the user, act is the agent (nested outermost-first when the chain is longer than one hop — RFC 8693 §4.1). The verdict is the strict intersection of what the user may do and what every actor may do — never the union. Adding a hop can only narrow authority.

verifyToken now REFUSES a delegated token

This is the part that matters most on a mobile client, and it is worth being blunt about. A delegated token has a real signature, the right issuer and audience, and a sub naming the user — so it verifies perfectly. Returning its claims would hand your app the user's full authority while silently discarding the bound scope of the agent that actually holds the token. That is the confused deputy, on-device.

So verifyToken rejects it, and says why. A malformed act is rejected just as firmly as a well-formed one: "unreadable" must never quietly become "not delegated".

There is deliberately no verifyDelegatedToken here

Delegated tokens are introspection-mandatory: only the server can confirm the delegation is still live (the grant not revoked, the user's session not ended), and RFC 7662 introspection requires an authenticated caller. A mobile app is a public client — it holds no secret to authenticate with, and shipping one would publish it (see the credential-model note above). So this SDK does not pretend to verify delegated tokens.

If your app receives one, hand it to your backend, which holds the credentials, does the introspection, and returns a plain answer. That is the same split the mobile security rules require of every AI feature: the device never holds the key, and the server re-validates.

checkDelegated / canDelegated / useDelegatedPermission

What the app can do is ask the PDP — which is exactly what you need to drive UI about agents: a consent screen previewing what an agent would be able to do, a "your agents" list, a button disabled because an agent cannot take that action for you.

import { useDelegatedPermission } from '@padosoft/laravel-iam-react-native';

function AgentDraftButton({ actors }: { actors: string[] }) {
  // actors = ['agent:assistant'] — CURRENT actor first
  const { allowed, loading } = useDelegatedPermission(actors, 'orders.draft', {
    type: 'order',
    id: orderId,
  });

  return <Button disabled={!allowed || loading} title="Let the assistant draft it" />;
}

Fail-closed exactly like usePermission: denied while loading, denied on any error, and denied without a network call when there is no subject or the actor chain is empty. An empty chain is never a fall-back to the plain user check — that would answer a question about the user when you asked about an agent.

The imperative forms are client.checkDelegated(subject, actors, permission, options?) and canDelegated(...).

Delegated decisions are never cached

Even with the cache enabled. A grant can be revoked at any moment, and a cached delegated allow would outlive the revocation meant to stop it. Plain checks cache exactly as before.

Reading the chain for display

inspectDelegatedBearer(jwt) parses a token locally — no Buffer, no node:crypto, UTF-8 safe — and returns { sub, actors, grantId, scopes }, or null when the token is not delegated. It is display and routing information, never authorization: use it to render "Agent X, acting for you", then let the PDP decide. A malformed act throws MalformedDelegationError rather than degrading.

Requires laravel-iam-agents on the server.

Managing permissions/roles? Not here — this SDK is a consumer

This package consumes decisions (it asks "can this subject do X?"); it does not own a permission catalog and does not declare or push manifests. A mobile/public client has no server-side catalog to sync. Permissions and roles are declared by the service that owns them (a Laravel app with the bridge, or a Node/Rust service, or the console) and synced there. See Keeping IAM in sync.

Ecosystem

| Package | Runtime | Description | Docs | |---------|---------|-------------|------| | laravel-iam-server | PHP/Laravel | The IAM server — the PDP itself (RBAC + ABAC + ReBAC, OAuth/OIDC, audit) | docs | | @padosoft/laravel-iam-node | Node 18+ | Core TypeScript/Node SDK — this package builds on its wire types | docs | | @padosoft/laravel-iam-react-native | React Native / React | This package | docs | | padosoft/laravel-iam-client | PHP 8.1+ | PHP client — the wire-contract reference | docs | | laravel-iam-rust | Rust | Rust client SDK (crate laravel-iam), async + blocking | docs |

License

MIT (c) Padosoft