---
title: Quickstart
description: A working sign-in, in about five minutes.
---

import Callout from '../../../components/Callout.astro';


This gets you a real sign-in screen backed by real sessions. It assumes React;
see [Next.js](/sdk/nextjs) if you are using the app router.

## 1. Create a project

In the [console](https://console.zevauth.com), create a project. You get a
development environment and a **publishable key** that looks like
`pk_test_…`.

## 2. Install

```bash
npm install @zevauth/react
```

## 3. Wrap your app

```tsx
import { ZevAuthProvider } from '@zevauth/react';

export function App() {
  return (
    <ZevAuthProvider publishableKey={import.meta.env.VITE_ZEVAUTH_KEY}>
      <YourApp />
    </ZevAuthProvider>
  );
}
```

## 4. Add a sign-in screen

```tsx
import { SignIn, SignedIn, SignedOut, UserButton } from '@zevauth/react';

function YourApp() {
  return (
    <>
      <SignedOut>
        <SignIn />
      </SignedOut>

      <SignedIn>
        <header>
          <UserButton />
        </header>
        <Dashboard />
      </SignedIn>
    </>
  );
}
```

That is a complete flow. `<SignIn>` renders whichever methods your environment
has enabled, styled with your brand.

<Callout type="info">
While the session is still being restored, neither `<SignedIn>` nor
`<SignedOut>` renders anything. That is deliberate. Treating "we do not know
yet" as signed out makes every page reload flash your signed-out UI. Use
`<ZevAuthLoading>` to show something during that moment.
</Callout>

## 5. Call your own API

```tsx
import { useAuth } from '@zevauth/react';

function SaveButton() {
  const { getToken } = useAuth();

  async function save() {
    const token = await getToken();
    await fetch('/api/notes', {
      method: 'POST',
      headers: { Authorization: `Bearer ${token}` },
      body: JSON.stringify({ text: 'hello' }),
    });
  }

  return <button onClick={save}>Save</button>;
}
```

`getToken()` refreshes the token first if it is close to expiring, so you never
have to think about it.

## 6. Verify it on your backend

```ts
import { verifyZevAuthToken } from '@zevauth/nextjs/server';

const claims = await verifyZevAuthToken(token, {
  environmentId: 'env_test_…',
});

// claims.sub is the user's id.
```

It is a standard JWT, so any library works. See
[Verifying tokens](/concepts/verifying-tokens) for other runtimes.

## What next

- Turn on more sign-in methods in the console. They appear in `<SignIn>`
  automatically.
- [Set your brand](/concepts/branding) so the screens look like your product.
- [Add organizations](/sdk/organizations) if your product has teams.