Skip to main content

React Webviews

The @mentra/react library simplifies building React-based webviews that integrate seamlessly with MentraOS authentication. When users open your webview through the MentraOS manager 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 manager 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 manager 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 manager 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 manager 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 manager 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 manager 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​