Aha! Builder applications come with user authentication built in. Your application can identify the logged-in user, gate pages and server functions behind a login, check roles for access control, and manage user accounts at the platform level. Elle sets this up when you describe who should be able to sign in and what they are allowed to do.
This reference documents the authentication API: currentUser(), userHasRole(), the Authenticate component, and the AhaUsers admin methods. Read it to understand how identity and roles work in your applications, to review what Elle generated, or to ground an AI assistant in the specifics — reference this article for Elle or any LLM so it knows exactly how authentication works in Aha! Builder.
Click any of the following links to skip ahead:
Overview
Authentication helpers for Builder Core applications.
User
Information about a user. In server functions, this is the local database users record.
Properties
id
id: number;The local database primary key from the users table. Use as a foreign key in queries.
authIdentifier?
optional authIdentifier: string;The Aha! platform user ID, stored as a string. Available in server functions. This matches the auth_identifier text column in the local users table.
avatarUrl?
optional avatarUrl: string;email: string;firstName
firstName: string;lastName
lastName: string;roles
roles: string[];The user's roles for role-based access control. New users default to ["user"]; the array may be empty if all roles have been removed. Synced from the Aha! platform on each login (overwritten by the platform — to change a user's roles, update them via the Builder app's authentication settings or via the AhaUsers API). Two default roles are always available: user and admin. Builders can define additional roles in the application settings.
For role checks in code, prefer userHasRole from @/auth. Pair with the top-level role server function option to gate handlers (see the server-functions README).
Authenticate
const Authenticate: React.FC<{
children?: React.ReactNode;
}>;Only display the children of the component if currentUser() returns
a non-null result, i.e. a user is logged in. If no user is logged in then
automatically redirect to the login page.
Since the component that renders <Authenticate> executes fully before its
children are gated, do not call currentUser() in the same component.
Place user-dependent code in a child component instead:
// WRONG — user may be null when RootLayout renders
function RootLayout({ children }) {
const user = currentUser();
return (
<Authenticate>
<span>{user.email}</span> {/* TypeError if not logged in */}
{children}
</Authenticate>
);
}
// CORRECT — access the user inside a child of <Authenticate>
function RootLayout({ children }) {
return (
<Authenticate>
<AuthenticatedLayout>{children}</AuthenticatedLayout>
</Authenticate>
);
}
function AuthenticatedLayout({ children }) {
const user = currentUser(); // safe — only renders when logged in
return (
<>
<span>{user.email}</span>
{children}
</>
);
}
currentUser()
function currentUser(): User | null;Returns the currently logged-in user from your app's local users table, or null if no user is logged in. Works in both client and server contexts:
Client: reads from
window.builderCore.currentUserServer functions: reads from the session cookie resolved by the server runtime
Important: currentUser() returns the local database user record. user.id is the primary key from your app's users table (e.g., 1, 2, 3...) — not the Aha! platform identifier. Use user.id directly as a foreign key in your queries. The authIdentifier field contains the Aha! platform user ID (BIGINT) and should not be confused with id.
The built-in user fields (firstName, lastName, email, avatarUrl, roles) are synced from the Aha! platform on each login. Direct changes to these fields via db.update() will be overwritten the next time the user logs in. To persist changes to these fields, use AhaUsers to update the Aha! platform record instead. Custom columns you add to usersTable (e.g., preferences, theme) are yours to control — they are not overwritten on login and can be updated locally via db.update().
const user = currentUser();
if (user) {
console.log(user.email);
}In a server function, use user.id to scope queries to the current user:
import { server, db, currentUser } from '@aha-app/builder-core';
import { plantsTable } from '@/db/schema';
import { eq } from 'drizzle-orm';
server.data('getPlants', async () => {
const user = currentUser();
if (!user) throw new Error('Not authenticated');
// ✅ Correct — user.id is the local database primary key
return await db
.select()
.from(plantsTable)
.where(eq(plantsTable.userId, user.id));
// ❌ Wrong — don't compare user.id with authIdentifier columns
// .where(eq(plantsTable.authIdentifier, user.id))
});To manage users at the platform level (create, update, delete, revoke sessions), use AhaUsers instead — see the AhaUsers section below.
userHasRole()
function userHasRole(role: Role | Role[]): boolean;Returns true if the currently logged-in user has the given role, false otherwise (including when no user is logged in). Pass an array to check for any of several roles (ANY-of match — matches the semantics of the top-level role server function option). Imported from @/auth module:
import { userHasRole } from '@/auth';
if (userHasRole('admin')) {
// show admin UI
}
if (userHasRole(['admin', 'editor'])) {
// show editor controls to admins or editors
}
redirectToLogin()
function redirectToLogin(): void;Send the user to the login page and then return to the root URL.
redirectToLogout()
function redirectToLogout(): void;Log the user out. After logout, the user is redirected
to returnTo (defaults to the root path).
AhaUsers (Server Functions Only)
Admin-level management of Aha!-managed authentication users. These are users stored in the Aha! platform database — not the local users table in your builder app's database. Only available in server functions.
currentUser() returns the local app user (with id: number from your app's users table). The AhaUsers API manages the Aha! platform user records that back authentication (with id: string, since Aha! IDs are BIGINTs).
AhaUsers.list()
function list(params?: { page?: number; perPage?: number }): ListAhaUsersResult;Returns { users, total, page, perPage }. Defaults to page 1 with 50 results.
import { server, AhaUsers } from '@aha-app/builder-core';
server.data('listUsers', async () => {
return AhaUsers.list({ page: 1, perPage: 25 });
});AhaUsers.findById()
function findById(id: string): AhaUser;Find an Aha! platform user by their ID. Throws if not found.
AhaUsers.create()
function create(params: CreateAhaUserParams): AhaUser;Create a new Aha! platform user. If password is omitted, a secure random password is generated.
server.data('createUser', async ({ email, firstName, lastName }) => {
return AhaUsers.create({ email, firstName, lastName });
});Param |
Type |
Required |
Description |
string |
Yes |
User email address |
|
firstName |
string |
No |
First name |
lastName |
string |
No |
Last name |
password |
string |
No |
Password (auto-generated if omitted) |
avatarUrl |
string |
No |
Avatar image URL |
AhaUser instance methods
AhaUser instances returned by list, findById, and create have these methods:
user.update()
function update(params: UpdateAhaUserParams): AhaUser;Update user profile attributes. Returns the updated user.
Param |
Type |
Required |
Description |
string |
No |
Email address |
|
firstName |
string |
No |
First name |
lastName |
string |
No |
Last name |
avatarUrl |
string |
No |
Avatar image URL |
user.updatePassword()
function updatePassword(params: UpdatePasswordParams): AhaUser;Change the user's password. Requires the current password for verification.
server.data('changePassword', async ({ userId, oldPassword, newPassword }) => {
const user = AhaUsers.findById(userId);
return user.updatePassword({
oldPassword,
password: newPassword,
passwordConfirmation: newPassword,
});
});Param |
Type |
Required |
Description |
oldPassword |
string |
Yes |
Current password |
password |
string |
Yes |
New password |
passwordConfirmation |
string |
Yes |
New password confirmation |
user.delete()
function delete(): void;Permanently delete the user.
user.revokeSessions()
function revokeSessions(): void;Revoke all active sessions, logging the user out everywhere.
AhaUser properties
Property |
Type |
Description |
id |
string |
Aha! platform user ID (BIGINT) |
string |
Email address |
|
firstName |
string |
First name |
lastName |
string |
Last name |
avatarUrl |
string? |
Avatar image URL |
emailVerified |
boolean |
Whether email has been verified |
createdAt |
string |
ISO 8601 creation timestamp |
updatedAt |
string |
ISO 8601 last update timestamp |
Build it with Elle
Describe the capability you want in plain language, and Elle, the Aha! Builder AI assistant, builds it into your application using the framework above.
New to this? Open Elle, ask what it can do, and try a capability in your application today.