The @mentra/react library simplifies building React-based webviews that integrate seamlessly with MentraOS authentication. When users open your webview through the MentraOS Mobile App, they are automatically authenticated without requiring a separate login process.

What Are React Webviews?

React webviews are web applications built with React that run inside the MentraOS Mobile App. They provide rich user interfaces for:
  • Settings and Configuration: Let users customize your app’s behavior
  • Data Visualization: Display charts, graphs, and analytics
  • Content Management: Create, edit, and organize user content
  • Dashboard Interfaces: Show personalized information and controls
The @mentra/react library handles all the authentication complexity, automatically extracting and verifying user tokens from the MentraOS system.

Complete Example

There’s a complete example of a React webview in the MentraOS-React-Example-App repository. Simply follow along with the README to get off the ground quickly.

Installation

Install the React authentication library in your webview project:
npm install @mentra/react
# or
yarn add @mentra/react
# or
bun add @mentra/react

Prerequisites

  • React 16.8+: The library uses React Hooks
  • MentraOS App Server: Your backend must be deployed, either on the same domain as your frontend or on a different domain that allows CORS requests from your frontend.
  • Developer Console Setup: Set the webview url in the developer console to your frontend server

Basic Setup

1. Wrap Your App with the Authentication Provider

The MentraAuthProvider component manages authentication state for your entire React application:
// src/main.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import {MentraAuthProvider} from "@mentra/react"

/**
 * Application entry point that provides MentraOS authentication context
 * to the entire React component tree
 */
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <MentraAuthProvider>
      <App />
    </MentraAuthProvider>
  </React.StrictMode>,
)

2. Access Authentication State

Use the UseMentraAuth hook to access user information and authentication status:
// src/components/UserProfile.tsx
import React from "react"
import {UseMentraAuth} from "@mentra/react"

/**
 * Component that displays user authentication status and profile information.
 * Demonstrates basic usage of the MentraOS authentication hook.
 *
 * @returns {React.JSX.Element} User profile component with authentication state
 */
const UserProfile: React.FC = (): React.JSX.Element => {
  const {userId, isLoading, error, isAuthenticated, logout} = UseMentraAuth()

  // Handle loading state during authentication
  if (isLoading) {
    return <div>Loading authentication...</div>
  }

  // Handle authentication errors
  if (error) {
    return (
      <div style={{color: "red"}}>
        <p>Authentication Error: {error}</p>
        <p>Please ensure you are opening this page from the MentraOS app.</p>
      </div>
    )
  }

  // Handle unauthenticated state
  if (!isAuthenticated || !userId) {
    return <div>Not authenticated. Please open from the MentraOS Mobile App.</div>
  }

  // Display authenticated user information
  return (
    <div>
      <h2>Welcome, MentraOS User!</h2>
      <p>
        User ID: <strong>{userId}</strong>
      </p>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

export default UserProfile

Complete Example Application

Here’s a comprehensive example that demonstrates authentication, API calls, and error handling:
/**
 * Main application component that demonstrates MentraOS authentication integration.
 * This file serves as the root component for testing the mentraos-react package functionality.
 */

import React, {useState} from "react"
import {MentraAuthProvider, UseMentraAuth} from "@mentra/react"

/**
 * Type definition for the API response from the notes endpoint
 */
interface NotesApiResponse {
  [key: string]: any // Allow flexible response structure
}

/**
 * Content component that displays authentication status and user information.
 * This component consumes the MentraAuth context to show loading states,
 * errors, and authenticated user data. It also provides functionality to make
 * authenticated API calls to the App backend.
 *
 * @returns {React.JSX.Element} The rendered content based on authentication state
 */
function Content(): React.JSX.Element {
  const {userId, isLoading, error, isAuthenticated, frontendToken} = UseMentraAuth()

  // State for managing API call results and loading state
  const [apiResult, setApiResult] = useState<NotesApiResponse | null>(null)
  const [apiError, setApiError] = useState<string | null>(null)
  const [isLoadingApi, setIsLoadingApi] = useState<boolean>(false)

  /**
   * Makes an authenticated API call to the app backend notes endpoint
   * Uses the frontendToken for authorization
   */
  const fetchNotesFromBackend = async (): Promise<void> => {
    if (!frontendToken) {
      setApiError("No frontend token available for backend call.")
      return
    }

    setIsLoadingApi(true)
    setApiError(null)
    setApiResult(null)

    try {
      const response = await fetch(`/api/notes`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${frontendToken}`,
        },
      })

      if (!response.ok) {
        throw new Error(`Backend request failed: ${response.status} ${response.statusText}`)
      }

      const data: NotesApiResponse = await response.json()
      setApiResult(data)
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
      setApiError(errorMessage)
    } finally {
      setIsLoadingApi(false)
    }
  }

  // Handle loading state
  if (isLoading) {
    return <p>Loading authentication...</p>
  }

  // Handle error state
  if (error) {
    return <p style={{color: "red"}}>Error: {error}</p>
  }

  // Handle unauthenticated state
  if (!isAuthenticated) {
    return <p>Not authenticated. Please log in.</p>
  }

  // Handle authenticated state
  return (
    <div>
      <p style={{color: "green"}}>✓ Successfully authenticated!</p>
      <p>
        User ID: <strong>{userId}</strong>
      </p>

      {/* API Testing Section */}
      <div style={{marginTop: "30px", padding: "20px", border: "1px solid #ccc", borderRadius: "8px"}}>
        <h2>Backend API Test</h2>
        <p>Test authenticated calls to your app backend:</p>

        <button
          onClick={fetchNotesFromBackend}
          disabled={isLoadingApi}
          style={{
            padding: "10px 20px",
            backgroundColor: isLoadingApi ? "#ccc" : "#007bff",
            color: "white",
            border: "none",
            borderRadius: "4px",
            cursor: isLoadingApi ? "not-allowed" : "pointer",
            fontSize: "16px",
          }}>
          {isLoadingApi ? "Loading..." : "Fetch Notes from Backend"}
        </button>

        {/* Display API loading state */}
        {isLoadingApi && <p style={{color: "#666", marginTop: "10px"}}>Making authenticated request...</p>}

        {/* Display API errors */}
        {apiError && (
          <div
            style={{
              marginTop: "15px",
              padding: "10px",
              backgroundColor: "#ffe6e6",
              border: "1px solid #ff9999",
              borderRadius: "4px",
            }}>
            <strong style={{color: "#cc0000"}}>API Error:</strong>
            <p style={{color: "#cc0000", margin: "5px 0 0 0"}}>{apiError}</p>
          </div>
        )}

        {/* Display API results */}
        {apiResult && (
          <div
            style={{
              marginTop: "15px",
              padding: "10px",
              backgroundColor: "#e6ffe6",
              border: "1px solid #99ff99",
              borderRadius: "4px",
            }}>
            <strong style={{color: "#006600"}}>API Response:</strong>
            <pre
              style={{
                backgroundColor: "#f5f5f5",
                padding: "10px",
                borderRadius: "4px",
                overflow: "auto",
                maxHeight: "300px",
                fontSize: "14px",
                fontFamily: 'Monaco, Consolas, "Courier New", monospace',
              }}>
              {JSON.stringify(apiResult, null, 2)}
            </pre>
          </div>
        )}
      </div>
    </div>
  )
}

/**
 * Root App component that provides authentication context to the entire application.
 * This component wraps the Content component with the MentraAuthProvider
 * to enable authentication functionality throughout the app.
 *
 * @returns {React.JSX.Element} The main application component
 */
function App(): React.JSX.Element {
  return (
    <MentraAuthProvider>
      <div style={{padding: "20px", fontFamily: "Arial, sans-serif"}}>
        <h1>MentraOS React Test App</h1>
        <Content />
      </div>
    </MentraAuthProvider>
  )
}

export default App

Making Authenticated API Calls

The frontendToken from UseMentraAuth is a JWT token that you should include in the Authorization header when making requests to your App backend:
/**
 * Hook for making authenticated API calls to the app backend
 *
 * @returns {Object} Functions for making authenticated requests
 */
const useAuthenticatedApi = () => {
  const {frontendToken} = UseMentraAuth()

  /**
   * Makes an authenticated GET request to the specified endpoint
   *
   * @param {string} endpoint - The API endpoint to call
   * @returns {Promise<any>} The response data
   * @throws {Error} If the request fails or token is missing
   */
  const authenticatedGet = async (endpoint: string): Promise<any> => {
    if (!frontendToken) {
      throw new Error("No authentication token available")
    }

    const response = await fetch(endpoint, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${frontendToken}`,
      },
    })

    if (!response.ok) {
      throw new Error(`API request failed: ${response.status} ${response.statusText}`)
    }

    return response.json()
  }

  /**
   * Makes an authenticated POST request with JSON data
   *
   * @param {string} endpoint - The API endpoint to call
   * @param {any} data - The data to send in the request body
   * @returns {Promise<any>} The response data
   * @throws {Error} If the request fails or token is missing
   */
  const authenticatedPost = async (endpoint: string, data: any): Promise<any> => {
    if (!frontendToken) {
      throw new Error("No authentication token available")
    }

    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${frontendToken}`,
      },
      body: JSON.stringify(data),
    })

    if (!response.ok) {
      throw new Error(`API request failed: ${response.status} ${response.statusText}`)
    }

    return response.json()
  }

  return {authenticatedGet, authenticatedPost}
}

Authentication Hook API

The UseMentraAuth hook returns an object with the following properties:
interface MentraAuthContextType {
  /** Unique identifier for the authenticated user */
  userId: string | null

  /** JWT token for making authenticated requests to your app backend */
  frontendToken: string | null

  /** True while authentication is being processed */
  isLoading: boolean

  /** Error message if authentication fails */
  error: string | null

  /** True if the user is successfully authenticated */
  isAuthenticated: boolean

  /** Function to clear authentication state and log out */
  logout: () => void
}

How Authentication Works

  1. Token Extraction: When the MentraOS Mobile App opens your webview, it appends an aos_signed_user_token parameter to the URL
  2. Token Verification: The library verifies the token’s signature against the MentraOS Cloud public key
  3. User Identification: If valid, it extracts the userId and frontendToken from the token payload
  4. Persistence: The authentication data is stored in localStorage for the session
  5. Context Sharing: All authentication state is made available through React Context

CORS Configuration

If your webview frontend is hosted separately from your app backend, configure CORS properly:
// In your app server setup
import cors from "cors"

/**
 * Configure CORS to allow requests from your webview domain
 */
app.use(
  cors({
    origin: [
      "https://your-webview-domain.com",
      "http://localhost:3000", // For development
    ],
    credentials: true, // If you use cookies
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
  }),
)

Troubleshooting

Common Issues

“MentraOS signed user token not found”
  • Ensure your webview is opened through the MentraOS Mobile App
  • Check that the manager app is correctly appending the token to your URL
  • Verify your webview URL is configured correctly in the Developer Console
“Token validation failed”
  • Verify the device clock is synchronized (token expiration is time-sensitive)
  • Check that you’re using the latest version of @mentra/react
  • Ensure the MentraOS Mobile App is up to date
Backend authentication fails
  • Check that you’re sending the Authorization: Bearer ${frontendToken} header
  • Verify your app server is configured to accept the frontend token
  • Ensure CORS is configured properly if frontend and backend are on different domains
Changes not reflecting
  • Clear browser cache and localStorage
  • Restart the MentraOS Mobile App
  • Check browser developer tools for JavaScript errors

Debugging Tips

Enable debug logging to troubleshoot authentication issues:
// Add this to see authentication state changes
const Content = () => {
  const auth = UseMentraAuth()

  // Log authentication state for debugging
  React.useEffect(() => {
    console.log("Auth state:", {
      userId: auth.userId,
      isAuthenticated: auth.isAuthenticated,
      isLoading: auth.isLoading,
      error: auth.error,
      hasToken: !!auth.frontendToken,
    })
  }, [auth])

  // ... rest of component
}

Next Steps