Skip to content

Code Examples

Complete, copy-paste examples for integrating Flowsta Auth into your application.

Quick Start (Any Framework)

The simplest integration - works with any JavaScript framework:

typescript
import { FlowstaAuth } from '@flowsta/auth';

const auth = new FlowstaAuth({
  clientId: 'your_client_id',
  redirectUri: window.location.origin + '/auth/callback'
});

// Login button click handler
document.getElementById('login-btn').onclick = () => auth.login();

// On your callback page (/auth/callback)
if (window.location.pathname === '/auth/callback') {
  auth.handleCallback().then(user => {
    console.log('Logged in:', user.displayName);
    window.location.href = '/dashboard';
  });
}

React

Basic React App

tsx
// src/App.tsx
import { FlowstaAuthProvider, useFlowstaAuth } from '@flowsta/auth/react';

function App() {
  return (
    <FlowstaAuthProvider
      clientId="your_client_id"
      redirectUri={window.location.origin + '/auth/callback'}
    >
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/auth/callback" element={<AuthCallback />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Router>
    </FlowstaAuthProvider>
  );
}

Login Component

tsx
// src/components/LoginButton.tsx
import { useFlowstaAuth } from '@flowsta/auth/react';

export function LoginButton() {
  const { isAuthenticated, user, login, logout, isLoading } = useFlowstaAuth();

  if (isLoading) {
    return <button disabled>Loading...</button>;
  }

  if (isAuthenticated) {
    return (
      <div className="user-menu">
        <img src={user.profilePicture} alt={user.displayName} />
        <span>{user.displayName}</span>
        {user.username && <span className="username">@{user.username}</span>}
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return (
    <button onClick={login} className="login-btn">
      Sign in with Flowsta
    </button>
  );
}

Auth Callback Page

tsx
// src/pages/AuthCallback.tsx
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useFlowstaAuth } from '@flowsta/auth/react';

export function AuthCallback() {
  const { handleCallback, error } = useFlowstaAuth();
  const navigate = useNavigate();

  useEffect(() => {
    handleCallback()
      .then(() => navigate('/dashboard'))
      .catch(err => console.error('Auth failed:', err));
  }, []);

  if (error) {
    return (
      <div className="error">
        <h2>Authentication Failed</h2>
        <p>{error}</p>
        <a href="/">Go back home</a>
      </div>
    );
  }

  return <div>Completing login...</div>;
}

Protected Route

tsx
// src/components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useFlowstaAuth } from '@flowsta/auth/react';

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isAuthenticated, isLoading } = useFlowstaAuth();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return <Navigate to="/" replace />;
  }

  return <>{children}</>;
}

// Usage:
<Route 
  path="/dashboard" 
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } 
/>

User Profile Display

tsx
// src/components/UserProfile.tsx
import { useFlowstaAuth } from '@flowsta/auth/react';

export function UserProfile() {
  const { user } = useFlowstaAuth();

  if (!user) return null;

  return (
    <div className="profile">
      <img 
        src={user.profilePicture} 
        alt={user.displayName}
        className="avatar"
      />
      <h2>{user.displayName}</h2>
      {user.username && <p className="username">@{user.username}</p>}
      {user.email && <p className="email">{user.email}</p>}
      <p className="did" title={user.did}>
        DID: {user.did?.substring(0, 20)}...
      </p>
    </div>
  );
}

Vue 3

Main App

vue
<!-- src/App.vue -->
<script setup lang="ts">
import { provide } from 'vue';
import { FlowstaAuth } from '@flowsta/auth';
import { RouterView } from 'vue-router';

const auth = new FlowstaAuth({
  clientId: 'your_client_id',
  redirectUri: window.location.origin + '/auth/callback'
});

provide('flowstaAuth', auth);
</script>

<template>
  <RouterView />
</template>

Composable Hook

typescript
// src/composables/useFlowstaAuth.ts
import { ref, inject, onMounted } from 'vue';
import type { FlowstaAuth, FlowstaUser } from '@flowsta/auth';

export function useFlowstaAuth() {
  const auth = inject<FlowstaAuth>('flowstaAuth')!;
  const user = ref<FlowstaUser | null>(null);
  const isAuthenticated = ref(false);
  const isLoading = ref(true);

  onMounted(() => {
    isAuthenticated.value = auth.isAuthenticated();
    user.value = auth.getUser();
    isLoading.value = false;
  });

  const login = () => auth.login();
  
  const logout = () => {
    auth.logout();
    user.value = null;
    isAuthenticated.value = false;
  };

  const handleCallback = async () => {
    const result = await auth.handleCallback();
    user.value = result;
    isAuthenticated.value = true;
    return result;
  };

  return {
    user,
    isAuthenticated,
    isLoading,
    login,
    logout,
    handleCallback
  };
}

Login Component

vue
<!-- src/components/LoginButton.vue -->
<script setup lang="ts">
import { useFlowstaAuth } from '@/composables/useFlowstaAuth';

const { isAuthenticated, user, login, logout, isLoading } = useFlowstaAuth();
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  
  <div v-else-if="isAuthenticated" class="user-menu">
    <img :src="user?.profilePicture" :alt="user?.displayName" />
    <span>{{ user?.displayName }}</span>
    <span v-if="user?.username" class="username">@{{ user.username }}</span>
    <button @click="logout">Logout</button>
  </div>
  
  <button v-else @click="login" class="login-btn">
    Sign in with Flowsta
  </button>
</template>

Auth Callback Page

vue
<!-- src/pages/AuthCallback.vue -->
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useFlowstaAuth } from '@/composables/useFlowstaAuth';

const router = useRouter();
const { handleCallback } = useFlowstaAuth();
const error = ref<string | null>(null);

onMounted(async () => {
  try {
    await handleCallback();
    router.push('/dashboard');
  } catch (err) {
    error.value = (err as Error).message;
  }
});
</script>

<template>
  <div v-if="error" class="error">
    <h2>Authentication Failed</h2>
    <p>{{ error }}</p>
    <router-link to="/">Go back home</router-link>
  </div>
  <div v-else>Completing login...</div>
</template>

Qwik

Root Layout

tsx
// src/routes/layout.tsx
import { component$, Slot, useContextProvider, createContextId } from '@builder.io/qwik';
import { FlowstaAuth } from '@flowsta/auth';

export const AuthContext = createContextId<FlowstaAuth>('flowsta-auth');

export default component$(() => {
  const auth = new FlowstaAuth({
    clientId: 'your_client_id',
    redirectUri: window.location.origin + '/auth/callback'
  });

  useContextProvider(AuthContext, auth);

  return <Slot />;
});

Login Component

tsx
// src/components/LoginButton.tsx
import { component$, useContext, useSignal, useVisibleTask$ } from '@builder.io/qwik';
import { AuthContext } from '~/routes/layout';
import type { FlowstaUser } from '@flowsta/auth';

export const LoginButton = component$(() => {
  const auth = useContext(AuthContext);
  const user = useSignal<FlowstaUser | null>(null);
  const isAuthenticated = useSignal(false);

  useVisibleTask$(() => {
    isAuthenticated.value = auth.isAuthenticated();
    user.value = auth.getUser();
  });

  return (
    <>
      {isAuthenticated.value ? (
        <div class="user-menu">
          <img src={user.value?.profilePicture} alt={user.value?.displayName} />
          <span>{user.value?.displayName}</span>
          {user.value?.username && (
            <span class="username">@{user.value.username}</span>
          )}
          <button onClick$={() => {
            auth.logout();
            window.location.href = '/';
          }}>
            Logout
          </button>
        </div>
      ) : (
        <button onClick$={() => auth.login()} class="login-btn">
          Sign in with Flowsta
        </button>
      )}
    </>
  );
});

Auth Callback Page

tsx
// src/routes/auth/callback/index.tsx
import { component$, useVisibleTask$, useSignal, useContext } from '@builder.io/qwik';
import { useNavigate } from '@builder.io/qwik-city';
import { AuthContext } from '~/routes/layout';

export default component$(() => {
  const auth = useContext(AuthContext);
  const nav = useNavigate();
  const error = useSignal<string | null>(null);

  useVisibleTask$(async () => {
    try {
      await auth.handleCallback();
      nav('/dashboard');
    } catch (err) {
      error.value = (err as Error).message;
    }
  });

  return (
    <div>
      {error.value ? (
        <div class="error">
          <h2>Authentication Failed</h2>
          <p>{error.value}</p>
          <a href="/">Go back home</a>
        </div>
      ) : (
        <div>Completing login...</div>
      )}
    </div>
  );
});

Vanilla JavaScript

HTML Page

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My App</title>
  <style>
    .hidden { display: none; }
    .user-menu { display: flex; align-items: center; gap: 10px; }
    .user-menu img { width: 40px; height: 40px; border-radius: 50%; }
    .username { color: #666; }
  </style>
</head>
<body>
  <header>
    <h1>My App</h1>
    <div id="auth-container">
      <button id="login-btn" class="hidden">Sign in with Flowsta</button>
      <div id="user-menu" class="hidden user-menu">
        <img id="user-avatar" src="" alt="">
        <span id="user-name"></span>
        <span id="user-username" class="username"></span>
        <button id="logout-btn">Logout</button>
      </div>
    </div>
  </header>

  <main id="content">
    <p>Welcome! Please sign in to continue.</p>
  </main>

  <script type="module">
    import { FlowstaAuth } from '@flowsta/auth';

    const auth = new FlowstaAuth({
      clientId: 'your_client_id',
      redirectUri: window.location.origin + '/auth/callback'
    });

    // DOM elements
    const loginBtn = document.getElementById('login-btn');
    const userMenu = document.getElementById('user-menu');
    const logoutBtn = document.getElementById('logout-btn');
    const userAvatar = document.getElementById('user-avatar');
    const userName = document.getElementById('user-name');
    const userUsername = document.getElementById('user-username');

    // Handle callback if on callback page
    if (window.location.pathname === '/auth/callback') {
      auth.handleCallback()
        .then(user => {
          console.log('Logged in:', user);
          window.location.href = '/';
        })
        .catch(err => {
          console.error('Auth failed:', err);
          alert('Login failed: ' + err.message);
          window.location.href = '/';
        });
    }

    // Update UI based on auth state
    function updateUI() {
      if (auth.isAuthenticated()) {
        const user = auth.getUser();
        loginBtn.classList.add('hidden');
        userMenu.classList.remove('hidden');
        userAvatar.src = user.profilePicture || '';
        userAvatar.alt = user.displayName || '';
        userName.textContent = user.displayName || '';
        userUsername.textContent = user.username ? `@${user.username}` : '';
      } else {
        loginBtn.classList.remove('hidden');
        userMenu.classList.add('hidden');
      }
    }

    // Event listeners
    loginBtn.addEventListener('click', () => auth.login());
    logoutBtn.addEventListener('click', () => {
      auth.logout();
      updateUI();
    });

    // Initialize
    updateUI();
  </script>
</body>
</html>

Node.js Backend

Express Server with Token Verification

javascript
// server.js
import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();
app.use(express.json());

// Middleware to verify Flowsta access tokens
async function verifyFlowstaToken(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];

  try {
    // Verify token with Flowsta
    const response = await fetch('https://auth-api.flowsta.com/oauth/userinfo', {
      headers: { 'Authorization': `Bearer ${token}` }
    });

    if (!response.ok) {
      return res.status(401).json({ error: 'Invalid token' });
    }

    const user = await response.json();
    req.user = user;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Token verification failed' });
  }
}

// Public endpoint
app.get('/api/public', (req, res) => {
  res.json({ message: 'This is public' });
});

// Protected endpoint
app.get('/api/profile', verifyFlowstaToken, (req, res) => {
  res.json({
    message: 'This is protected',
    user: {
      id: req.user.sub,
      name: req.user.name,
      username: req.user.preferred_username,
      email: req.user.email,
      did: req.user.did
    }
  });
});

// Protected endpoint - only users with username
app.get('/api/premium', verifyFlowstaToken, (req, res) => {
  if (!req.user.preferred_username) {
    return res.status(403).json({ 
      error: 'Username required',
      message: 'Please set a username in your Flowsta profile'
    });
  }

  res.json({
    message: 'Welcome premium user!',
    username: req.user.preferred_username
  });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Making Authenticated API Calls (Frontend)

typescript
// api.ts
import { FlowstaAuth } from '@flowsta/auth';

const auth = new FlowstaAuth({
  clientId: 'your_client_id',
  redirectUri: window.location.origin + '/auth/callback'
});

async function fetchWithAuth(url: string, options: RequestInit = {}) {
  const token = auth.getAccessToken();
  
  if (!token) {
    throw new Error('Not authenticated');
  }

  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });

  if (response.status === 401) {
    // Token expired - redirect to login
    auth.logout();
    auth.login();
    throw new Error('Session expired');
  }

  return response;
}

// Usage
async function getProfile() {
  const response = await fetchWithAuth('/api/profile');
  return response.json();
}

async function updateSettings(settings: object) {
  const response = await fetchWithAuth('/api/settings', {
    method: 'POST',
    body: JSON.stringify(settings)
  });
  return response.json();
}

Next Steps

Need Help?

Documentation licensed under CC BY-SA 4.0.