Skip to content

User Storage Modes

Auth always owns security state. The database mode controls who owns user profile records.

ModeBest forAuth-owned tables
managedApps that want Auth to own the user profile and metadata shape.layeron_auth_users, layeron_auth_identities, layeron_auth_credentials, layeron_auth_sessions, layeron_auth_refresh_tokens, layeron_auth_oauth_states
managed_coreApps that want Auth to own identity basics and keep application profile data elsewhere.layeron_auth_users, layeron_auth_identities, layeron_auth_credentials, layeron_auth_sessions, layeron_auth_refresh_tokens, layeron_auth_oauth_states
mappedApps that store user profiles in a Database product table.layeron_auth_identities, layeron_auth_credentials, layeron_auth_sessions, layeron_auth_refresh_tokens, layeron_auth_oauth_states
customApps that already own a user table and need custom lifecycle hooks.layeron_auth_identities, layeron_auth_credentials, layeron_auth_sessions, layeron_auth_refresh_tokens, layeron_auth_oauth_states
externalApps that resolve user profiles through a function and want Auth to own login state.layeron_auth_identities, layeron_auth_credentials, layeron_auth_sessions, layeron_auth_refresh_tokens, layeron_auth_oauth_states

All Auth tables live in an internal Layeron Database Product store with namespace: "layeron".

managed stores the complete Auth user shape:

FieldColumn
idid
emailemail
emailVerifiedAtemail_verified_at
phonephone
phoneVerifiedAtphone_verified_at
primaryIdentityIdprimary_identity_id
usernameusername
displayNamedisplay_name
avatarUrlavatar_url
defaultTenantIddefault_tenant_id
isAnonymousis_anonymous
rolesroles
scopesscopes
attributesattributes
statusstatus
appMetadataapp_metadata
userMetadatauser_metadata
createdAtcreated_at
updatedAtupdated_at
lastSignInAtlast_sign_in_at
disabledAtdisabled_at

Use managed when the Auth user record can be the main user profile for the backend.

Terminal window
const appAuth = auth({
database: {
mode: "managed",
},
})

managed_core stores only the stable identity fields:

FieldColumn
idid
emailemail
emailVerifiedAtemail_verified_at
phonephone
phoneVerifiedAtphone_verified_at
primaryIdentityIdprimary_identity_id
usernameusername
displayNamedisplay_name
avatarUrlavatar_url
statusstatus
createdAtcreated_at
updatedAtupdated_at
lastSignInAtlast_sign_in_at
disabledAtdisabled_at

Use managed_core when application data owns tenant membership, roles, profile metadata, and domain-specific user settings.

Terminal window
const appAuth = auth({
database: {
mode: "managed_core",
},
})

custom stores session records and calls your functions for user records.

Terminal window
const appAuth = auth({
database: {
mode: "custom",
async getUser({ userId }) {
return await users.findById(userId)
},
async createUser({ email, emailVerifiedAt, user }) {
return await users.create({
email,
emailVerifiedAt,
displayName: user.displayName,
})
},
async isUserEnabled({ user }) {
return user.status !== "disabled"
},
async updateLastSignIn({ userId, signedInAt }) {
await users.updateLastSignIn(userId, signedInAt)
},
async updateEmailVerifiedAt({ userId, emailVerifiedAt }) {
await users.updateEmailVerifiedAt(userId, emailVerifiedAt)
},
},
})

The getUser function must return the same id that Auth requested. Auth rejects the session when the user is missing, disabled, or returned with a different id.

Email sign-up in custom mode requires createUser. OTP verification in custom mode requires updateEmailVerifiedAt.

mapped stores the user profile in your Database product and stores Auth security state in Auth’s internal Database product store. Auth queries the mapped table by idColumn whenever it creates, verifies, refreshes, or resolves a session.

Terminal window
import { auth, db } from "@layeron/modules"
const usersDb = db({
name: "users",
namespace: "app",
})
const appAuth = auth({
database: {
mode: "mapped",
product: usersDb,
users: {
table: "profiles",
idColumn: "id",
emailColumn: "email",
displayNameColumn: "display_name",
metadataColumn: "metadata",
},
},
})

Mapped table and column names must be SQL identifiers. Email/password sign-up in mapped mode requires userId; the mapped user’s email must match the sign-up email.

external stores login state in Auth and resolves user profiles through resolveUser.

Terminal window
const appAuth = auth({
database: {
mode: "external",
async resolveUser({ userId, request }) {
return await users.resolveProfile({
userId,
request,
})
},
},
})

The resolver must return the same id that Auth requested. Email/password sign-up in external mode requires userId; the resolved user’s email must match the sign-up email.

Every mode stores email/password login identities in layeron_auth_identities. The identity row separates login providers from the user record.

FieldColumn
idid
userIduser_id
providerprovider
providerUserIdprovider_user_id
emailemail
phonephone
verifiedAtverified_at
linkedAtlinked_at
lastSignInAtlast_sign_in_at
identityDataidentity_data

Every mode stores the same session fields:

FieldColumn
idid
userIduser_id
access token hashaccess_token_hash
devicedevice
createdAtcreated_at
expiresAtexpires_at
lastUsedAtlast_used_at
revokedAtrevoked_at

Every mode stores refresh tokens as hashes. Refresh token rotation is enabled by default. Reuse detection can revoke the whole token family.

FieldColumn
idid
userIduser_id
sessionIdsession_id
refresh token hashtoken_hash
family idfamily_id
parent token hashparent_token_hash
createdAtcreated_at
expiresAtexpires_at
lastUsedAtlast_used_at
revokedAtrevoked_at
replacedAtreplaced_at
reusedAtreused_at
devicedevice