Understanding authentication in Base44 apps
1. TricklAI Admin Platform (THIS APP)
2. Micro Apps (Built by other developers)
📝 Summary:
TricklAI Admin: Small number of admins, invited manually
Micro Apps: Unlimited end users, they handle their own auth
This applies ONLY to the TricklAI admin dashboard (this app):
✅ For Micro Apps (separate apps):
Micro apps can create their own login/signup flows for their end users!
1. User Entity is Built-In
Every Base44 app has a User entity automatically created with these fields:
idemailfull_namerole (either 'admin' or 'user')created_date2. Users Are Invited, Not Registered
Users don't "sign up" - they're invited through the Base44 dashboard:
3. Login Redirect
When a user needs to login, redirect them to the Base44 login page:
import { base44 } from "@/api/base44Client";
// Redirect to login (will return to current page after login)
base44.auth.redirectToLogin();
// Or redirect to a specific page after login
base44.auth.redirectToLogin(createPageUrl("Dashboard"));Step 1: Micro App Has Their Own Users
Example: "Equipment Rentals App" can have signup/login however they want:
// Equipment Rentals App can have normal signup
export default function Signup() {
const handleSignup = async (email, password) => {
// Create user in THEIR database
await theirOwnAuth.createUser(email, password);
};
}Step 2: User Opts In to SMS
When user wants SMS notifications, redirect to TricklAI opt-in page:
// In Equipment Rentals App <a href="https://apps.tricklai.com/pages/SMSOptIn?app=equipment-rentals"> Enable SMS Notifications </a> // Or embed the opt-in form in their app // User enters phone and consents - stored in TricklAI
Step 3: Micro App Sends SMS
Equipment Rentals App calls TricklAI API with THEIR api key:
// Equipment Rentals backend
fetch('https://apps.tricklai.com/functions/sendSMS', {
method: 'POST',
headers: {
'x-trickl-api-key': 'their_api_key_from_tricklai',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone_number: '+1234567890',
message: 'Your rental is ready for pickup!'
})
});
// TricklAI checks:
// 1. Is the API key valid? ✓
// 2. Does this app have SMS enabled? ✓
// 3. Has this user opted in? ✓
// Then sends the SMS!✅ Use Base44 Built-in Auth When:
// For TricklAI admin pages
useEffect(() => {
base44.auth.me()
.then(setUser)
.catch(() => base44.auth.redirectToLogin());
}, []);✅ Build Your Own Auth When:
// For micro apps - can use any auth
// Firebase, Auth0, Clerk, or custom
export default function Signup() {
const handleSignup = async () => {
await yourAuth.signUp(email, password);
// Then optionally redirect to TricklAI for SMS opt-in
};
}✅ Pattern 1: Check if user is logged in
import { base44 } from "@/api/base44Client";
import { useEffect, useState } from "react";
import { createPageUrl } from "@/utils";
export default function MyPage() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const checkAuth = async () => {
try {
const currentUser = await base44.auth.me();
setUser(currentUser);
} catch (error) {
// Not logged in - redirect to login
base44.auth.redirectToLogin(createPageUrl("MyPage"));
}
setLoading(false);
};
checkAuth();
}, []);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Welcome, {user.full_name}!</h1>
</div>
);
}✅ Pattern 2: Public pages that work for everyone
import { base44 } from "@/api/base44Client";
import { useEffect, useState } from "react";
export default function PublicPage() {
const [user, setUser] = useState(null);
useEffect(() => {
const checkAuth = async () => {
try {
const currentUser = await base44.auth.me();
setUser(currentUser);
} catch (error) {
// Not logged in - that's okay for public pages
console.log("User not logged in");
}
};
checkAuth();
}, []);
return (
<div>
<h1>Public Content</h1>
{user ? (
<p>Welcome back, {user.full_name}!</p>
) : (
<button onClick={() => base44.auth.redirectToLogin()}>
Login
</button>
)}
</div>
);
}✅ Pattern 3: Logout
import { base44 } from "@/api/base44Client";
// Simple logout (reloads the page)
<button onClick={() => base44.auth.logout()}>
Logout
</button>
// Logout and redirect to specific page
<button onClick={() => base44.auth.logout(createPageUrl("Home"))}>
Logout
</button>❌ Creating a Login page
// DON'T DO THIS for TricklAI Admin!
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async () => {
// This will NOT work in Base44 for admin users!
await someLoginFunction(email, password);
};
// ... rest of login form
}Instead: Use base44.auth.redirectToLogin()
❌ Creating a signup/register page
// DON'T DO THIS for TricklAI Admin!
export default function Signup() {
const handleSignup = async () => {
await base44.entities.User.create({
email: email,
password: password
});
};
// ... rest of signup form
}Instead: Admin users are invited through the Base44 dashboard
❌ Trying to manage authentication state manually
// DON'T DO THIS for TricklAI Admin! const [isAuthenticated, setIsAuthenticated] = useState(false); const [authToken, setAuthToken] = useState(null); // Manual authentication management causes loops!
Instead: Use base44.auth.me() and let Base44 handle the session
You CAN add custom fields to the Base44 User entity, but DON'T override built-in fields:
// ✅ CORRECT: Add custom fields to User entity
// File: entities/User.json
{
"name": "User",
"type": "object",
"properties": {
"phone_number": {
"type": "string",
"description": "User's phone number"
},
"company": {
"type": "string",
"description": "User's company name"
},
"preferences": {
"type": "object",
"description": "User preferences"
}
}
}
// Note: DON'T include id, email, full_name, role - they're automatic!To update user data:
import { base44 } from "@/api/base44Client";
// Update current user's custom fields
await base44.auth.updateMe({
phone_number: "+1234567890",
company: "Acme Corp"
});
// Access the data
const user = await base44.auth.me();
console.log(user.phone_number); // "+1234567890"Base44 handles this automatically
<button onClick={() => base44.auth.redirectToLogin()}>
Login
</button>useEffect(() => {
base44.auth.me()
.then(setUser)
.catch(() => base44.auth.redirectToLogin());
}, []);Dashboard → Users → Invite User