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

@smarthivelabs-devs/auth-expo

v1.5.0

Published

SmartHive Auth provider, hooks, and SecureStore integration for React Native / Expo

Readme

@smarthivelabs-devs/auth-expo

SmartHive Auth for React Native and Expo. Provides a provider, hooks, and components with SecureStore-backed token storage.

Supports two sign-in modes — both in the same package, zero config difference:

| Mode | How it works | Good for | |---|---|---| | Headless | Call signIn.* directly — no browser, custom UI | Native mobile apps with branded login screens | | OAuth redirect | login() opens system browser, deep-links back | Social login, SSO, or when you want the hosted UI |


Installation

npx expo install @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store
# npm / pnpm
npm install @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store
pnpm add @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store

Peer dependencies: expo-auth-session>=5, expo-secure-store>=12, react>=18, react-native>=0.73


Setup

Wrap your root component with SmartHiveAuthProvider. The redirectUri is only needed for the OAuth redirect flow — you can still set it even if you only use headless.

// app/_layout.tsx
import { SmartHiveAuthProvider, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";

export default function Layout() {
  return (
    <SmartHiveAuthProvider
      projectId={process.env.EXPO_PUBLIC_AUTH_PROJECT_ID!}
      publishableKey={process.env.EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY!}
      baseUrl={process.env.EXPO_PUBLIC_AUTH_BASE_URL!}
      redirectUri={buildRedirectUri("myapp")}
    >
      <Stack />
    </SmartHiveAuthProvider>
  );
}
# .env
EXPO_PUBLIC_AUTH_PROJECT_ID=proj_abc123
EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY=pk_prod_abc123
EXPO_PUBLIC_AUTH_BASE_URL=https://auth.myapp.com

Headless Sign-in (Custom Login Screen)

No browser, no redirect. Call the method, get tokens. Full control of your UI.

Email + Password

import { useAuth } from "@smarthivelabs-devs/auth-expo";
import { useState } from "react";
import { Button, TextInput, View, Text } from "react-native";

export default function LoginScreen() {
  const { signIn } = useAuth();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  async function handleSignIn() {
    try {
      await signIn.email({ email, password });
      // Session is saved automatically — user is now signed in
    } catch (e: any) {
      setError(e.message);
    }
  }

  return (
    <View>
      <TextInput value={email} onChangeText={setEmail} placeholder="Email" />
      <TextInput value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry />
      {error ? <Text>{error}</Text> : null}
      <Button title="Sign in" onPress={handleSignIn} />
    </View>
  );
}

Phone OTP

import { useAuth } from "@smarthivelabs-devs/auth-expo";
import { useState } from "react";
import { Button, TextInput, View } from "react-native";

export default function PhoneLoginScreen() {
  const { signIn } = useAuth();
  const [phone, setPhone] = useState("");
  const [code, setCode] = useState("");
  const [step, setStep] = useState<"phone" | "code">("phone");

  async function sendOtp() {
    await signIn.phone.sendOtp({ phoneNumber: phone });
    setStep("code");
  }

  async function verifyOtp() {
    await signIn.phone.verify({ phoneNumber: phone, code });
    // Signed in — session saved automatically
  }

  if (step === "phone") {
    return (
      <View>
        <TextInput value={phone} onChangeText={setPhone} placeholder="+1234567890" keyboardType="phone-pad" />
        <Button title="Send code" onPress={sendOtp} />
      </View>
    );
  }

  return (
    <View>
      <TextInput value={code} onChangeText={setCode} placeholder="Enter code" keyboardType="number-pad" />
      <Button title="Verify" onPress={verifyOtp} />
    </View>
  );
}

Email OTP

const { signIn } = useAuth();

// Step 1 — send the code
await signIn.emailOtp.send({ email: "[email protected]" });

// Step 2 — verify (returns session, user is now signed in)
await signIn.emailOtp.verify({ email: "[email protected]", code: "123456" });

Magic Link

const { signIn } = useAuth();

// Sends an email — user clicks the link to sign in (no token returned here)
await signIn.magicLink.send({ email: "[email protected]" });

Headless Sign-up

import { useAuth } from "@smarthivelabs-devs/auth-expo";

const { signUp } = useAuth();

const result = await signUp.email({
  email: "[email protected]",
  password: "secret123",
  name: "Jane Doe",       // optional
});

if (result.requiresVerification) {
  // Email verification required — show "check your inbox" screen
  // No session yet, tokens are empty
} else {
  // Account created and signed in immediately
}

Resend verification email

If the user did not receive the initial verification email — or the link expired — call signUp.resendVerificationEmail to trigger a fresh one:

import { useAuth } from "@smarthivelabs-devs/auth-expo";

const { signUp } = useAuth();

// Called from a "Didn't receive the email? Resend" button
await signUp.resendVerificationEmail({ email: "[email protected]" });

Throws SmartHiveAuthError with code resend_failed if the server rejects the request.


Social OAuth Sign-in (Google, Apple, GitHub, etc.)

Each social provider uses your project's own credentials — the consent screen shows your app name. Configure credentials in your SmartHive dashboard under Project → OAuth Providers, then call:

import { useAuth } from "@smarthivelabs-devs/auth-expo";
import { Button } from "react-native";

export default function LoginScreen() {
  const { signIn } = useAuth();

  return (
    <>
      <Button title="Continue with Google" onPress={() => signIn.social("google")} />
      <Button title="Continue with Apple"  onPress={() => signIn.social("apple")} />
      <Button title="Continue with GitHub" onPress={() => signIn.social("github")} />
    </>
  );
}

signIn.social() calls Linking.openURL() to open the provider's consent screen in the system browser. When the user approves, the provider redirects back to your app's redirectUri deep link with ?access_token=...&refresh_token=.... The SmartHiveAuthProvider deep link listener picks this up automatically and saves the session — no extra setup needed.

Supported providers: google · apple · github · facebook · twitter · linkedin · microsoft · discord · spotify · twitch · reddit · gitlab · slack · notion · zoom · figma

Deep link required — make sure redirectUri is set to your app scheme (e.g. myapp://auth/callback) and your scheme is registered in app.json. See the Deep Link Setup section below.


OAuth Redirect Sign-in (SmartHive hosted page)

The original PKCE flow — redirects to the SmartHive hosted login page and back. Use it for SSO or when you want the hosted login UI.

import { useAuth } from "@smarthivelabs-devs/auth-expo";
import { Button } from "react-native";

export default function LoginScreen() {
  const { login } = useAuth();
  return <Button title="Sign in with SmartHive" onPress={() => login()} />;
}

Deep Link Setup (required for OAuth redirect only)

If you use login(), register a custom scheme in app.json so the browser can redirect back:

{
  "expo": {
    "scheme": "myapp",
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [{ "scheme": "myapp" }],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

Rebuild after changing app.json:

npx expo prebuild

No extra setup needed for headless sign-in — it works without a deep link.


Sign-out

const { logout } = useAuth();

// Clears SecureStore + invalidates session on the server
await logout();

Hooks

useAuth()

Returns the full auth context.

const {
  session,              // AuthSession | null
  isLoaded,             // true once initial SecureStore read is done
  isSignedIn,           // boolean
  login,                // OAuth redirect sign-in (PKCE, SmartHive hosted page)
  logout,               // sign out
  signIn,               // headless + social sign-in methods
  signUp,               // headless sign-up (email, resendVerificationEmail)
  refreshSession,       // force a token refresh
  authFetch,            // authenticated fetch wrapper
  getAuthorizationHeader, // () => Promise<{ authorization: string }>
} = useAuth();

// Social sign-in is under signIn.social:
await signIn.social("google");
await signIn.social("apple");
await signIn.social("github");

useSession()

const session = useSession(); // AuthSession | null
// session.accessToken, session.refreshToken, session.expiresAt, session.user

useUser()

const user = useUser(); // unknown | null

useIsLoaded()

true once the initial SecureStore check is complete. Use this to avoid a flash of unauthenticated state on startup.

const isLoaded = useIsLoaded();
if (!isLoaded) return <SplashScreen />;

useIsSignedIn()

Returns true (signed in), false (signed out), or null (still loading).

const isSignedIn = useIsSignedIn();

useEffect(() => {
  if (isSignedIn === false) router.replace("/login");
}, [isSignedIn]);

useAuthFetch()

Authenticated fetch wrapper. Bearer token is injected automatically and refreshed transparently when near expiry.

const authFetch = useAuthFetch();
const res = await authFetch("https://api.myapp.com/protected");

useAuthorizationHeader()

Resolves to { authorization: "Bearer <token>" }. Useful for GraphQL clients or custom SDK setup.

const getAuthorizationHeader = useAuthorizationHeader();
const headers = await getAuthorizationHeader();

Render Helpers

import { SignedIn, SignedOut, AuthLoading } from "@smarthivelabs-devs/auth-expo";

// Shown only when loaded + authenticated
<SignedIn><Dashboard /></SignedIn>

// Shown only when loaded + not authenticated
<SignedOut><LoginScreen /></SignedOut>

// Shown while the initial SecureStore check is running
<AuthLoading><ActivityIndicator /></AuthLoading>

Expo Router Integration

app/
├── _layout.tsx           ← SmartHiveAuthProvider here
├── index.tsx             ← SignedIn / SignedOut routing
├── login.tsx             ← your custom login screen using signIn.*
└── (protected)/
    └── dashboard.tsx
// app/_layout.tsx
import { Stack } from "expo-router";
import { SmartHiveAuthProvider, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";

export default function Layout() {
  return (
    <SmartHiveAuthProvider
      projectId={process.env.EXPO_PUBLIC_AUTH_PROJECT_ID!}
      publishableKey={process.env.EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY!}
      baseUrl={process.env.EXPO_PUBLIC_AUTH_BASE_URL!}
      redirectUri={buildRedirectUri("myapp")}
    >
      <Stack />
    </SmartHiveAuthProvider>
  );
}
// app/index.tsx
import { SignedIn, SignedOut, AuthLoading } from "@smarthivelabs-devs/auth-expo";
import { Redirect } from "expo-router";
import { ActivityIndicator } from "react-native";

export default function Index() {
  return (
    <>
      <AuthLoading><ActivityIndicator /></AuthLoading>
      <SignedIn><Redirect href="/dashboard" /></SignedIn>
      <SignedOut><Redirect href="/login" /></SignedOut>
    </>
  );
}
// app/login.tsx — custom screen, no browser redirect
import { useAuth } from "@smarthivelabs-devs/auth-expo";
import { useState } from "react";
import { Button, TextInput, View, Text, StyleSheet } from "react-native";

export default function LoginScreen() {
  const { signIn } = useAuth();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  async function handleSignIn() {
    setLoading(true);
    setError("");
    try {
      await signIn.email({ email, password });
    } catch (e: any) {
      setError(e.message ?? "Sign in failed.");
    } finally {
      setLoading(false);
    }
  }

  return (
    <View style={styles.container}>
      <TextInput style={styles.input} value={email} onChangeText={setEmail} placeholder="Email" autoCapitalize="none" />
      <TextInput style={styles.input} value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry />
      {error ? <Text style={styles.error}>{error}</Text> : null}
      <Button title={loading ? "Signing in…" : "Sign in"} onPress={handleSignIn} disabled={loading} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", padding: 24 },
  input: { borderWidth: 1, borderColor: "#ccc", borderRadius: 8, padding: 12, marginBottom: 12 },
  error: { color: "red", marginBottom: 12 },
});

Token Storage

All tokens are stored in expo-secure-store:

  • iOS: Keychain Services
  • Android: Android Keystore (AES encryption)

PKCE verifier and state (used during OAuth redirect flow) are also stored in SecureStore and deleted after the code exchange completes.


Provider Props

| Prop | Type | Required | Description | |---|---|---|---| | projectId | string | Yes | Your SmartHive project ID | | publishableKey | string | Yes | Your publishable key (pk_prod_*) | | baseUrl | string | Yes | URL of your SmartHive Auth service | | redirectUri | string | Yes | Deep link callback URI — used only for OAuth redirect flow | | authDomain | string | No | Custom branded auth domain | | children | ReactNode | Yes | Your app tree |


TypeScript Types

import type {
  SmartHiveExpoConfig,
  SmartHiveAuthProviderProps,
} from "@smarthivelabs-devs/auth-expo";

import type {
  AuthSession,
  HeadlessClient,
  HeadlessSignInResult,
  HeadlessSignUpResult,
  SmartHiveAuthClient,
} from "@smarthivelabs-devs/auth-sdk";

Low-level: initExpoAuth

Direct client without the provider (advanced use):

import { initExpoAuth, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";

const client = initExpoAuth({
  projectId: "proj_abc123",
  publishableKey: "pk_prod_abc123",
  baseUrl: "https://auth.myapp.com",
  redirectUri: buildRedirectUri("myapp"),
});

await client.initialize();

// Headless sign-in
const session = await client.headless.signIn.email({ email, password });

// OAuth redirect
await client.login();

Social Auth Proxy (White-label Domain)

By default, when a user taps "Sign in with Google", the iOS system prompt shows:

"YourApp wants to use authcore.smarthivelabs.dev to sign in"

And the Google consent screen shows:

"to continue to smarthivelabs.dev"

To show your own domain on both prompts, add two thin proxy routes to your backend and set socialProxyUrl in the provider config. SmartHive Auth does all the OAuth work — your domain just acts as the visible entry point.

Step 1 — Add proxy routes to your backend

// src/routes/socialProxyRoutes.ts
import express from "express";
const router = express.Router();

const SMARTHIVE_BASE = process.env.SMARTHIVE_AUTH_BASE_URL!; // https://authcore.smarthivelabs.dev
const SMARTHIVE_ENV  = process.env.SMARTHIVE_AUTH_ENVIRONMENT!; // prod
const SMARTHIVE_PID  = process.env.SMARTHIVE_PROJECT_ID!;
const APP_BASE_URL   = process.env.APP_BASE_URL!; // https://api.yourapp.com

// Initiation — app calls this, proxy forwards to SmartHive
router.get("/:provider", async (req, res) => {
  const target = new URL(`${SMARTHIVE_BASE}/${SMARTHIVE_ENV}/api/auth/social/${req.params.provider}`);
  target.searchParams.set("project_id", SMARTHIVE_PID);
  if (req.query.redirect_uri) target.searchParams.set("redirect_uri", req.query.redirect_uri as string);
  target.searchParams.set("proxy_callback", `${APP_BASE_URL}/api/auth/social/${req.params.provider}/callback`);
  res.redirect(target.toString());
});

// Callback — Google/Apple calls this, proxy forwards to SmartHive
router.get("/:provider/callback", async (req, res) => {
  const target = new URL(`${SMARTHIVE_BASE}/${SMARTHIVE_ENV}/api/auth/social/${req.params.provider}/callback`);
  for (const [k, v] of Object.entries(req.query)) target.searchParams.set(k, v as string);
  res.redirect(target.toString());
});

app.use("/api/auth/social", router);

Required env vars on your backend:

SMARTHIVE_AUTH_BASE_URL=https://authcore.smarthivelabs.dev
SMARTHIVE_AUTH_ENVIRONMENT=prod
APP_BASE_URL=https://api.yourapp.com

Step 2 — Register your callback URL with each provider

In your Google Cloud Console (or Apple/GitHub/etc. developer console), register:

https://api.yourapp.com/api/auth/social/google/callback

instead of the SmartHive URL. Replace google with each provider you support.

Step 3 — Set socialProxyUrl in your app

<SmartHiveAuthProvider
  publishableKey="pk_prod_..."
  projectId="your-project-id"
  baseUrl="https://authcore.smarthivelabs.dev"
  redirectUri="myapp://auth/callback"
  socialProxyUrl="https://api.yourapp.com"   // ← add this
>
  <App />
</SmartHiveAuthProvider>

That's it. signIn.social("google") now opens api.yourapp.com/api/auth/social/google, iOS shows your domain, and Google shows "to continue to yourapp.com".


Related Packages

| Package | Use case | |---|---| | @smarthivelabs-devs/auth-sdk | Core SDK — framework-agnostic | | @smarthivelabs-devs/auth-react | React web apps | | @smarthivelabs-devs/auth-server | Express / Next.js server-side JWT verification |


License

MIT © SmartHive Labs