Authentication flow

Understanding the complete sign-in and sign-up process

Beta

The Managed Better Auth is in Beta. Share your feedback on Discord or via the OptiTech Console.

This guide explains the authentication flow: how sign-in works from SDK call to session creation.

note

Anyone can sign up for your application by default. Support for restricted signups is coming soon. Until then, consider adding a verification step by enabling email verification via verification link or verification code.

Architecture overview

Managed Better Auth is a managed REST API service built on Better Auth that connects directly to your OptiTech database. You use the SDK in your application and configure settings in the Console; no servers to manage.

Your App (SDK)
    ↓ HTTP requests
Managed Better Auth Service (REST API)
    ↓ connects to database
Your OptiTech Database (optitech_auth schema)

All authentication data (users, sessions, OAuth configurations) lives in your database's optitech_auth schema. You can query these tables directly with SQL for debugging, analytics, or custom logic.

Complete sign-in flow

Here's what happens when a user signs in, from your code to the database:

  1. User signs in

    Your application calls the SDK's sign-in method:

    const { data, error } = await client.auth.signIn.email({
      email: 'user@example.com',
      password: 'password',
    });

    The SDK posts to {OPTITECH_AUTH_URL}/auth/sign-in/email. The Auth service validates credentials against optitech_auth.account, creates a session in optitech_auth.session, and returns the session cookie with user data.

    Response you receive:

    {
      data: {
        session: {
          access_token: "eyJhbGc...",  // JWT token
          expires_at: 1763848395,
          // ... other session fields
        },
        user: {
          id: "dc42fa70-09a7-4038-a3bb-f61dda854910",
          email: "user@example.com",
          emailVerified: true,
          // ... other user fields
        }
      }
    }
  2. The Auth service sets an HTTP-only cookie (__Secure-optitechauth.session_token) in your browser. This cookie:

    • Contains an opaque session token (not a JWT)
    • Is automatically sent with every request to the Auth API
    • Is secure (HTTPS only, HttpOnly, SameSite=None)
    • Is managed entirely by the SDK; you never touch it

    Where to see it: Open DevTools → Application → Cookies → look for __Secure-optitechauth.session_token

  3. JWT token is retrieved

    The SDK automatically retrieves a JWT token and stores it in session.access_token. You don't need to call /auth/token separately; the SDK handles this behind the scenes.

    What's in the JWT:

    {
      "sub": "dc42fa70-09a7-4038-a3bb-f61dda854910", // User ID
      "email": "user@example.com",
      "role": "authenticated",
      "exp": 1763848395, // Expiration timestamp
      "iat": 1763847495 // Issued at timestamp
    }

    The sub claim contains the user ID from optitech_auth.user.id. This is what Row Level Security policies use to identify the current user.

  4. JWT is used for database queries

    When you query your database via Data API, the SDK automatically includes the JWT in the Authorization header:

    // JWT is automatically included in Authorization header
    const { data } = await client.from('posts').select('*');

    What happens:

    1. SDK gets JWT from session.access_token
    2. Adds Authorization: Bearer <jwt-token> header
    3. Data API validates JWT signature using JWKS public keys
    4. Data API extracts user ID from JWT and makes it available to RLS policies
    5. Your query runs with the authenticated user context

Sign-up flow

The sign-up flow creates a new user:

const { data, error } = await client.auth.signUp.email({
  email: 'newuser@example.com',
  password: 'securepassword',
  name: 'New User',
});

The SDK posts to {OPTITECH_AUTH_URL}/auth/sign-up/email. The Auth service creates a new row in optitech_auth.user, stores hashed credentials in optitech_auth.account, and returns user data. If email verification is required, it creates a verification token in optitech_auth.verification and may delay session creation until verification.

note

By default, anyone can sign up for your application. To add an additional verification layer, enable email verification (see Email Verification). Built-in signup restrictions are coming soon.

OAuth flow

OAuth authentication (Google, GitHub, Vercel, etc.):

await client.auth.signIn.social({
  provider: 'google',
  callbackURL: 'http://localhost:3000/auth/callback',
});

The SDK redirects to the OAuth provider. After the user authenticates, the provider redirects back with an authorization code. The SDK exchanges the code for an access token, then the Auth service creates or updates the user in optitech_auth.user, stores OAuth tokens in optitech_auth.account, and creates a session.

Database as source of truth

Managed Better Auth stores all data in your database's optitech_auth schema:

  • Changes are immediate (no sync delays)
  • Query auth data directly with SQL
  • Each branch has isolated auth data
  • You own your data
SELECT id, email, "emailVerified", "createdAt"
FROM optitech_auth.user
ORDER BY "createdAt" DESC;

Data API integration

When you enable the Data API, JWT tokens from Managed Better Auth are validated automatically. Two helper functions make the user ID available to RLS policies. Use the one that matches your column type:

  • auth.user_id(): returns sub as text
  • auth.uid(): returns sub as uuid (returns NULL if sub isn't a valid UUID)

The Data API also works with other authentication providers that issue JWTs, such as Auth0, Clerk, and Firebase.

Example RLS policy:

CREATE POLICY "Users can view own posts"
ON posts FOR SELECT TO authenticated
USING (user_id = auth.user_id());

Learn more about securing your data:

What's next

Need help?

Join our Discord Server to ask questions or see what others are doing with OptiTech. For paid plan support options, see Support.

Was this page helpful?