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 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
- Token Extraction: When the MentraOS Mobile App opens your webview, it appends an
aos_signed_user_tokenparameter to the URL - Token Verification: The library verifies the token's signature against the MentraOS Cloud public key
- User Identification: If valid, it extracts the
userIdandfrontendTokenfrom the token payload - Persistence: The authentication data is stored in
localStoragefor the session - 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
- Complete Example Application: See a complete example of a React webview
- Deploying a React App: This guide covers deploying the MentraOS React Example App to production
- Webview Authentication Overview: Learn about the broader authentication system
- Core Concepts: Understand the full MentraOS ecosystem