> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scute.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Javascript

# Integration with Scute JS Core

Scute provides a comprehensive authentication solution for web applications through the `@scute/js-core` package. This guide covers implementing multiple authentication methods including passkeys (WebAuthn), magic links, OTP verification, and OAuth providers. While the examples use React, the same patterns and API calls work with any web framework including Vue, Angular, Svelte, or vanilla JavaScript.

## Example Application

A complete working example is available in the [Scute JS repository](https://github.com/scuteai/js/tree/main/examples/with-react-and-core). This example demonstrates all the authentication flows covered in this guide, including:

* **Multi-method Authentication**: Passkey, magic link, OTP, and OAuth sign-in
* **Complete User Interface**: Forms for each authentication method with proper state management
* **Session Management**: User profile display with active session management
* **Device Registration**: WebAuthn device registration flow with skip option
* **Error Handling**: Comprehensive error handling with user-friendly messages
* **URL Management**: Proper handling of magic link callbacks and URL cleanup

The example app uses Vite + React + TypeScript and provides a practical reference implementation that you can adapt for your own web applications regardless of framework. You can clone the repository and run the example locally to see all authentication flows in action.

## Prerequisites

* Node.js 16+ and npm/yarn/pnpm
* A Scute application configured in your dashboard
* Basic knowledge of JavaScript/TypeScript and your chosen web framework

## Installation

Install the Scute JS Core package:

```bash theme={null}
npm install @scute/js-core
```

For React applications, also install React dependencies:

```bash theme={null}
npm install react react-dom
npm install -D @types/react @types/react-dom  # For TypeScript projects
```

For other frameworks, install the appropriate dependencies for your chosen framework (Vue, Angular, Svelte, etc.).

## Quick Start

### 1. Client Configuration

Create a Scute client instance to handle all authentication operations:

```typescript theme={null}
// lib/scute.ts
import { createClient } from "@scute/js-core";

export const scuteClient = createClient({
  appId: process.env.VITE_SCUTE_APP_ID!,
  baseUrl: process.env.VITE_SCUTE_BASE_URL!,
});
```

### 2. Environment Variables

Configure your environment variables (`.env`):

```env theme={null}
VITE_SCUTE_APP_ID=your_app_id_here
VITE_SCUTE_BASE_URL=https://your-scute-instance.com
```

> **Note**: The `VITE_` prefix is required for Vite-based projects. Adjust the prefix according to your build tool (e.g., `REACT_APP_` for Create React App).

### 3. Basic Authentication Flow

Implement a complete authentication system with state management. The following example uses React, but the same authentication logic applies to any web framework:

```typescript theme={null}
import React, { useState, useEffect } from "react";
import {
  ScuteClient,
  type ScuteTokenPayload,
  type ScuteUserData,
  getMeaningfulError,
} from "@scute/js-core";
import { scuteClient } from "./lib/scute";

function App() {
  const [currentView, setCurrentView] = useState<string>("loading");
  const [user, setUser] = useState<ScuteUserData | null>(null);

  useEffect(() => {
    checkAuthStatus();
    handleMagicLinkCallback();
  }, []);

  const checkAuthStatus = async () => {
    const { data, error } = await scuteClient.getSession();

    if (error) {
      console.error("Session check failed:", error);
      setCurrentView("login");
      return;
    }

    if (data?.session?.status === "authenticated") {
      setUser(data.user);
      setCurrentView("dashboard");
    } else {
      setCurrentView("login");
    }
  };

  const handleMagicLinkCallback = () => {
    const magicToken = scuteClient.getMagicLinkToken();
    if (magicToken) {
      setCurrentView("verifying");
      verifyMagicLink(magicToken);
    }
  };

  // Render different views based on current state
  return (
    <div className="app">
      {currentView === "loading" && <LoadingView />}
      {currentView === "login" && <LoginView onSuccess={checkAuthStatus} />}
      {currentView === "dashboard" && <DashboardView user={user} />}
      {currentView === "verifying" && <VerifyingView />}
    </div>
  );
}
```

## Authentication Flows

Scute supports three main authentication flows, each designed for different use cases and user preferences. All flows integrate seamlessly with passkey (WebAuthn) authentication when devices are registered.

### 1. Magic Link Flow

The magic link flow allows users to authenticate via email without remembering passwords. When a user enters their email address, a secure magic link is sent to their inbox. Upon clicking the link, they are automatically signed in or signed up if they don't have an account.

#### Initial Login Attempt

The `signInOrUp` method first attempts passkey authentication. If the user has a registered device, the browser will prompt them to use their passkey instead of sending a magic link. If the user does not have a registered device, the method will send a magic link to the user's email address:

```typescript theme={null}
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  const { data, error } = await scuteClient.signInOrUp(identifier);

  if (error) {
    console.log("signInOrUp error");
    return console.log({
      data,
      error,
      meaningfulError: error && getMeaningfulError(error),
    });
  }

  if (!data) {
    // Passkey verified successfully - user has registered device
    setComponent("profile");
  } else {
    setComponent("magic_sent");
  }
};
```

#### Sending Magic Links

You can also force sending a magic link regardless of registered devices:

```typescript theme={null}
const handleSendMagicLink = async () => {
  await scuteClient.sendLoginMagicLink(identifier);
  setComponent("magic_sent");
};
```

#### Magic Link Confirmation

After sending the magic link, show a confirmation message:

```typescript theme={null}
const MagicSent = ({ identifier }: { identifier: string }) => {
  return (
    <div className="card">
      <h5>Magic Link Sent</h5>
      <p>
        Please check <strong>{identifier}</strong> for the magic link.
      </p>
    </div>
  );
};
```

#### Magic Link Verification

When users click the magic link, your application automatically detects and verifies the token:

```typescript theme={null}
// Detect magic link token on app load
useEffect(() => {
  const magicLinkToken = scuteClient.getMagicLinkToken();
  if (magicLinkToken) {
    setComponent("magic_verify");
    setMagicLinkToken(magicLinkToken);
  }
}, [scuteClient]);

// Verification component
const MagicVerify = ({
  scuteClient,
  setComponent,
  magicLinkToken,
  setTokenPayload,
}: {
  scuteClient: ScuteClient;
  setComponent: (component: string) => void;
  magicLinkToken: string | null;
  setTokenPayload: (tokenPayload: ScuteTokenPayload | null) => void;
}) => {
  const url = new URL(window.location.href);
  const shouldSkipDeviceRegistration = !!url.searchParams.get(SCUTE_SKIP_PARAM);
  const verificationStarted = useRef(false);

  useEffect(() => {
    const verifyMagicLink = async () => {
      if (!magicLinkToken) {
        return console.log("no magic link token found");
      }

      const { data, error } = await scuteClient.verifyMagicLinkToken(
        magicLinkToken
      );
      if (error) {
        console.log("verifyMagicLink error");
        return console.log({
          data,
          error,
          meaningfulError: error && getMeaningfulError(error),
        });
      }

      setTokenPayload(data.authPayload);
      setComponent("register_device");

      url.searchParams.delete(SCUTE_MAGIC_PARAM);
      window.history.replaceState({}, "", url.toString());
    };

    if (!verificationStarted.current) {
      verificationStarted.current = true;
      verifyMagicLink();
    }
  }, []);

  return (
    <div className="card">
      <h5>Verifying Magic Link...</h5>
    </div>
  );
};
```

#### Device Registration

After successful magic link verification, users can be prompted to register their device for future passkey authentication. Users can also be given the option to skip device registration and proceed directly to their profile. This enables passwordless login using WebAuthn for subsequent authentications:

```typescript theme={null}
export const RegisterDevice = ({
  scuteClient,
  tokenPayload,
  setComponent,
}: {
  scuteClient: ScuteClient;
  tokenPayload: ScuteTokenPayload | null;
  setComponent: (component: string) => void;
}) => {
  const handleRegisterDevice = async () => {
    if (!tokenPayload) {
      console.error("No token payload");
      return;
    }
    const { error: signInError } = await scuteClient.signInWithTokenPayload(
      tokenPayload
    );
    if (signInError) {
      console.log("signInWithTokenPayload error");
      console.log({
        signInError,
        meaningfulError: getMeaningfulError(signInError),
      });
      return;
    }
    const { data, error } = await scuteClient.addDevice();
    if (error) {
      console.log("addDevice error");
      console.log({ data, error, meaningfulError: getMeaningfulError(error) });
      return;
    }
    setComponent("profile");
  };

  const handleSkipDeviceRegistration = async () => {
    if (!tokenPayload) {
      console.error("No token payload");
      return;
    }
    const { error: signInError } = await scuteClient.signInWithTokenPayload(
      tokenPayload
    );
    if (signInError) {
      console.log("signInWithTokenPayload error");
      console.log({
        signInError,
        meaningfulError: getMeaningfulError(signInError),
      });
      return;
    }
    setComponent("profile");
  };

  return (
    <div className="card">
      <h5>Register Device</h5>
      <button onClick={handleRegisterDevice}>Register Device</button>
      <button onClick={handleSkipDeviceRegistration}>
        Skip Device Registration
      </button>
    </div>
  );
};
```

### 2. OTP Flow

The OTP (One-Time Password) flow works with both email addresses and phone numbers. The identifier type can be configured in your Scute dashboard. After entering their identifier, users receive a verification code that must be entered to complete authentication.

#### OTP Verification Form

```typescript theme={null}
const OtpForm = ({
  scuteClient,
  identifier,
  setComponent,
  setTokenPayload,
}: {
  scuteClient: ScuteClient;
  identifier: string;
  setComponent: (component: string) => void;
  setTokenPayload: (tokenPayload: ScuteTokenPayload | null) => void;
}) => {
  const [otp, setOtp] = useState("");

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const { data, error } = await scuteClient.verifyOtp(otp, identifier);
    if (error) {
      console.log("verifyOtp error");
      console.log({ data, error, meaningfulError: getMeaningfulError(error) });
      return;
    }

    if (data) {
      setTokenPayload(data.authPayload);
      setComponent("register_device");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h5>Enter OTP</h5>
      <input
        type="text"
        placeholder="OTP"
        value={otp}
        onChange={(e) => setOtp(e.target.value)}
      />
      <button type="submit">Verify OTP</button>
    </form>
  );
};
```

Like the magic link flow, if the user already has a registered device, the browser will prompt them to use their passkey during the initial login attempt. After successful OTP verification, users are presented with the option to register their device for future passkey authentication.

### 3. OAuth Flow

The OAuth flow enables users to authenticate through third-party providers like Google. This flow is unique because after successful third-party authentication, Scute redirects back to your application with both a magic link token and a skip parameter.

#### Initiating OAuth Authentication

```typescript theme={null}
const handleSignInWithGoogle = async () => {
  await scuteClient.signInWithOAuthProvider("google");
};
```

#### OAuth Callback Handling

Upon successful authentication with the third-party provider, Scute redirects back to your application with:

* A `sct_magic` parameter containing the authentication token
* A `sct_sk` parameter that differentiates OAuth magic links from email magic links

Your magic verification method can handle OAuth callbacks as well. It also sends a `sct_sk` parameter which you can use to determine the app behaviour based on whether it is an emailed magic link or an OAuth magic link. Below example uses the `sct_sk` parameter to skip the device registration step for OAuth flows.

```typescript theme={null}
const shouldSkipDeviceRegistration = !!url.searchParams.get(SCUTE_SKIP_PARAM);

if (!shouldSkipDeviceRegistration && data?.authPayload) {
  // Prompt for device registration (rare for OAuth)
  setTokenPayload(data.authPayload);
  setComponent("register_device");
} else {
  // Direct sign-in (typical for OAuth flows)
  setComponent("profile");
}
```

The `sct_sk` parameter is OAuth-specific.

## Session Management

Handle user sessions and authentication state:

```typescript theme={null}
// Get current session
const getCurrentSession = async () => {
  const { data, error } = await scuteClient.getSession();

  if (error) {
    console.error("Failed to get session:", error);
    return null;
  }

  return data;
};

// Sign out user
const signOut = async () => {
  try {
    await scuteClient.signOut();
    setUser(null);
    setCurrentView("login");
  } catch (error) {
    console.error("Sign out failed:", error);
  }
};

// Revoke specific session
const revokeSession = async (sessionId: string) => {
  try {
    await scuteClient.revokeSession(sessionId);
    // Refresh user data to update sessions list
    await refreshUserData();
  } catch (error) {
    console.error("Failed to revoke session:", error);
  }
};
```

## Error Handling

Implement comprehensive error handling for authentication flows:

```typescript theme={null}
import { getMeaningfulError } from "@scute/js-core";

const handleAuthError = (error: any) => {
  const meaningfulError = getMeaningfulError(error);

  // Log for debugging
  console.error("Authentication error:", meaningfulError);

  // Show user-friendly message
  setErrorMessage(
    meaningfulError || "Authentication failed. Please try again."
  );

  // Handle specific error types
  if (error.code === "INVALID_CREDENTIALS") {
    // Handle invalid credentials
  } else if (error.code === "NETWORK_ERROR") {
    // Handle network issues
  }
};
```

## TypeScript Support

Scute provides comprehensive TypeScript definitions:

```typescript theme={null}
import type {
  ScuteClient,
  ScuteSession,
  ScuteUserData,
  ScuteTokenPayload,
  AuthResponse,
  SessionResponse,
} from "@scute/js-core";

// Use types for better development experience
const handleAuthResponse = (response: AuthResponse) => {
  if (response.error) {
    // Handle error
    return;
  }

  // response.data is properly typed
  console.log(response.data);
};
```
