Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@swig-wallet/expo-idp-sdk

Minimal Expo-compatible Swig IdP SDK scaffold.

Public API (v1)

  • SwigIdpProvider
  • useSwigIdp()
  • createSwigWebClient() from @swig-wallet/expo-idp-sdk/web
  • public types from provider.tsx

Package structure

src/
  hooks/
    use-swig-idp.ts
  proof/
    proof-pipeline.ts
  states/
    states.ts
    bootstrap.ts
    begin-auth.ts
    complete-auth.ts
    start-oauth.ts
    start-email-otp.ts
    oauth-callback.ts
  swig-session/
    session-service.ts
    session-store.ts
  transport/
    api.ts
  web.ts
  config.ts
  index.ts
  provider.tsx

Usage

import { SwigIdpProvider, useSwigIdp } from "@swig-wallet/expo-idp-sdk";

function LoginButton() {
  const { startOAuth, isAuthenticated, authPhase } = useSwigIdp();

  if (isAuthenticated) {
    return null;
  }

  return (
    <button
      onClick={async () => {
        await startOAuth({
          provider: "google",
          clientId: "your-client-id",
          policyId: "your-policy-id",
          flow: "role",
        });
      }}
    >
      {authPhase === "begin_oauth" ? "Opening browser..." : "Continue"}
    </button>
  );
}

export function App() {
  return (
    <SwigIdpProvider
      config={{
        redirectUri: "yourapp://auth/callback",
      }}
    >
      <LoginButton />
    </SwigIdpProvider>
  );
}

Email OTP

function EmailLogin() {
  const { startEmailOtp, authPhase } = useSwigIdp();

  return (
    <button
      onClick={async () => {
        await startEmailOtp({
          // provider defaults to "email_otp"; pass an explicit value if a
          // tenant has registered its own email-otp provider key.
          clientId: "your-client-id",
          policyId: "your-policy-id",
          flow: "role",
        });
      }}
    >
      {authPhase === "begin_oauth" ? "Opening browser..." : "Continue with email"}
    </button>
  );
}

startEmailOtp opens the isolated host's email entry page in a system auth session — the user types their address inside the IH, the IH sends the code, and the same OTP entry + verify + callback flow runs from there. The address, the verification code, and the callback JWT never enter the developer app. The function resolves with the same session payload OAuth produces.

SMS OTP

function SmsLogin() {
  const { startSmsOtp, authPhase } = useSwigIdp();

  return (
    <button
      onClick={async () => {
        await startSmsOtp({
          // provider defaults to "sms"; pass an explicit value if a tenant
          // has registered its own sms-otp provider key.
          clientId: "your-client-id",
          policyId: "your-policy-id",
          flow: "role",
        });
      }}
    >
      {authPhase === "begin_oauth" ? "Opening browser..." : "Continue with phone"}
    </button>
  );
}

startSmsOtp opens the isolated host's phone entry page in a system auth session — the user types their phone number inside the IH, the IH sends the SMS code, and the same OTP entry + verify + callback flow runs from there. The phone number, verification code, and callback JWT never enter the developer app. The function resolves with the same session payload OAuth produces.

Web usage

Use the /web entry point in browser apps. This path does not import Expo modules.

import { Network, createSwigWebClient } from "@swig-wallet/expo-idp-sdk/web";

const swig = createSwigWebClient({
  redirectUri: `${window.location.origin}/auth/callback`,
  network: Network.Devnet,
});

await swig.redirectToOAuth({
  provider: "google",
  clientId: "your-client-id",
  policyId: "your-policy-id",
  flow: "role",
});

In your callback route:

import { createSwigWebClient } from "@swig-wallet/expo-idp-sdk/web";

const swig = createSwigWebClient();
const session = await swig.completeOAuthFromUrl(window.location.href);

console.log(session.configAddress, session.walletAddress, session.roleId);

Useful web client methods:

  • getOAuthStartUrl(input) builds the isolated host URL without navigating.
  • redirectToOAuth(input) performs a full-page browser redirect.
  • completeOAuthFromUrl(url) parses and persists the callback session.
  • createSigner(input) signs prepared Solana transactions in a visible isolated-host approval window.
  • getSession() returns persisted session data.
  • getPersistedSession() returns the full persisted session.
  • logout() clears the stored session.
  • listProviders({ clientId }) lists configured IdP providers.

Web session persistence defaults to window.localStorage. Pass a custom storage adapter to use sessionStorage, cookies, or framework-managed storage.

Managed-session callbacks include the isolated host's Ed25519 public key and a bounded expiration. The corresponding non-exportable private key remains on the isolated-host origin. createSigner() requires that managed-session metadata, opens the isolated host for explicit approval, and returns the transaction only after the isolated host signs it.

Notes

  • proof/*, swig-session/*, states/*, and transport/* are internal modules and not part of the stable SDK contract.
  • Expo session persistence defaults to expo-secure-store. Pass a custom storage adapter in config to override.
  • baseUrl is optional and defaults to https://backend.prod.infra.onswig.com.
  • isolatedHostUrl is optional and defaults to https://swig-dev-portal-isolated-host.vercel.app.
  • Install expo-secure-store in the host Expo/React Native app.
  • High-level public flows are startOAuth(), startEmailOtp(), and startSmsOtp(). All three return the same PersistedSwigSession shape.
  • transport/api.ts is a thin 1:1 wrapper over api_idp.proto and api_wallet.proto HTTP mappings.
  • OAuth start now prefers a backend-issued start_token for the isolated host redirect, and falls back to the legacy raw redirect params only when the backend has not been upgraded yet.
  • Mobile auth is intentionally routed through expo-web-browser system auth sessions via openAuthSessionAsync.
  • Embedded WebView auth is unsupported. Do not load the isolated host inside react-native-webview or any host-controlled in-app browser if you need production security guarantees.
  • Business logic remains scaffolded with TODOs in:
    • proof/proof-pipeline.ts
    • swig-session/session-service.ts
    • states/complete-auth.ts
  • completeAuth() accepts the signup shape (client_id, network, zk_proof).
  • Proof refreshes return transient ProgramExecProof requester authority metadata and do not create session-key roles.
  • Auth phases:
    • start
    • begin_oauth
    • complete_oauth
    • begin_swig_jwt
    • end_swig_jwt
    • begin_proof
    • end_proof
    • begin_session_exchange
    • swig_program_session_started
    • authenticated
    • error
  • TODO areas:
    • proof generation
    • session exchange
    • session lifecycle policy
  • Default endpoint mapping:
    • listProviders -> /identity/api/providers
    • startAuth -> /identity/api/auth/start
    • startEmailOtp -> /identity/api/auth/email/start
    • startSmsOtp -> /identity/api/auth/sms/start
    • signup -> /identity/api/signup
    • lookupSwig -> /wallet/swig/lookup
    • getSwigStatus -> /wallet/swig/status
    • checkSwigAuth -> /wallet/swig/auth/check
    • createSwigSession -> /wallet/swig/session
    • getPolicy -> /wallet/policies/{policy_id}

Security boundary

  • The SDK's supported mobile auth paths are startOAuth(), startEmailOtp(), and startSmsOtp(). All open the isolated host in a system auth session.
  • Do not embed the isolated host in a WebView. A host app that owns the WebView can inspect DOM, URLs, and storage. The OTP code, callback JWT, and the user's email or phone number are all held inside the isolated host on purpose.
  • If you need a custom mobile integration, preserve the same boundary: backend start token -> isolated host -> system auth session -> deep link callback.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages