Auth Entities
Wasp supports multiple different authentication methods and for each method, we need to store different information about the user. For example, if you are using the Username & password authentication method, we need to store the user's username and password. On the other hand, if you are using the Email authentication method, you will need to store the user's email, password and for example, their email verification status.
Entities Explained
To store user information, Wasp creates a few entities behind the scenes. In this section, we will explain what entities are created and how they are connected.
User Entity
When you want to add authentication to your app, you need to specify the user entity e.g. User
in your Wasp file. This entity is a "business logic user" which represents a user of your app.
You can use this entity to store any information about the user that you want to store. For example, you might want to store the user's name or address. You can also use the user entity to define the relations between users and other entities in your app. For example, you might want to define a relation between a user and the tasks that they have created.
entity User {=psl
id Int @id @default(autoincrement())
// Any other fields you want to store about the user
psl=}
You own the user entity and you can modify it as you wish. You can add new fields to it, remove fields from it, or change the type of the fields. You can also add new relations to it or remove existing relations from it.
On the other hand, the Auth
, AuthIdentity
and Session
entities are created behind the scenes and are used to store the user's login credentials. You as the developer don't need to care about this entity most of the time. Wasp owns these entities.
In the case you want to create a custom signup action, you will need to use the Auth
and AuthIdentity
entities directly.
Example App Model
Let's imagine we created a simple tasks management app:
- The app has email and Google-based auth.
- Users can create tasks and see the tasks that they have created.
Let's look at how would that look in the database:
If we take a look at an example user in the database, we can see:
- The business logic user,
User
is connected to multipleTask
entities.- In this example, "Example User" has two tasks.
- The
User
is connected to exactly oneAuth
entity. - Each
Auth
entity can have multipleAuthIdentity
entities.- In this example, the
Auth
entity has twoAuthIdentity
entities: one for the email-based auth and one for the Google-based auth.
- In this example, the
- Each
Auth
entity can have multipleSession
entities.- In this example, the
Auth
entity has oneSession
entity.
- In this example, the
Wasp currently doesn't support multiple auth identities for a single user. This means, for example, that a user can't have both an email-based auth identity and a Google-based auth identity. This is something we will add in the future with the introduction of the account merging feature.
Account merging means that multiple auth identities can be merged into a single user account. For example, a user's email and Google identity can be merged into a single user account. Then the user can log in with either their email or Google account and they will be logged into the same account.
Auth
Entity internal
Wasp's internal Auth
entity is used to connect the business logic user, User
with the user's login credentials.
entity Auth {=psl
id String @id @default(uuid())
userId Int? @unique
// Wasp injects this relation on the User entity as well
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
identities AuthIdentity[]
sessions Session[]
psl=}
The Auth
fields:
id
is a unique identifier of theAuth
entity.userId
is a foreign key to theUser
entity.- It is used to connect the
Auth
entity with the business logic user.
- It is used to connect the
user
is a relation to theUser
entity.- This relation is injected on the
User
entity as well.
- This relation is injected on the
identities
is a relation to theAuthIdentity
entity.sessions
is a relation to theSession
entity.
AuthIdentity
Entity internal
The AuthIdentity
entity is used to store the user's login credentials for various authentication methods.
entity AuthIdentity {=psl
providerName String
providerUserId String
providerData String @default("{}")
authId String
auth Auth @relation(fields: [authId], references: [id], onDelete: Cascade)
@@id([providerName, providerUserId])
psl=}
The AuthIdentity
fields:
providerName
is the name of the authentication provider.- For example,
email
orgoogle
.
- For example,
providerUserId
is the user's ID in the authentication provider.- For example, the user's email or Google ID.
providerData
is a JSON string that contains additional data about the user from the authentication provider.- For example, for password based auth, this field contains the user's hashed password.
- This field is a
String
and not aJson
type because Prisma doesn't support theJson
type for SQLite.
authId
is a foreign key to theAuth
entity.- It is used to connect the
AuthIdentity
entity with theAuth
entity.
- It is used to connect the
auth
is a relation to theAuth
entity.
Session
Entity internal
The Session
entity is used to store the user's session information. It is used to keep the user logged in between page refreshes.
entity Session {=psl
id String @id @unique
expiresAt DateTime
userId String
auth Auth @relation(references: [id], fields: [userId], onDelete: Cascade)
@@index([userId])
psl=}
The Session
fields:
id
is a unique identifier of theSession
entity.expiresAt
is the date when the session expires.userId
is a foreign key to theAuth
entity.- It is used to connect the
Session
entity with theAuth
entity.
- It is used to connect the
auth
is a relation to theAuth
entity.
Accessing the Auth Fields
If you are looking to access the user's email or username in your code, you can do that by accessing the info about the user that is stored in the AuthIdentity
entity.
Everywhere where Wasp gives you the user
object, it also includes the auth
relation with the identities
relation. This means that you can access the auth identity info by using the user.auth.identities
array.
To make things a bit easier for you, Wasp offers a few helper functions that you can use to access the auth identity info.
getEmail
The getEmail
helper returns the user's email or null
if the user doesn't have an email auth identity.
- JavaScript
- TypeScript
import { getEmail } from 'wasp/auth'
const MainPage = ({ user }) => {
const email = getEmail(user)
// ...
}
import { getEmail } from 'wasp/auth'
export const createTask = async (args, context) => {
const email = getEmail(context.user)
// ...
}
import { getEmail, AuthUser } from 'wasp/auth'
const MainPage = ({ user }: { user: AuthUser }) => {
const email = getEmail(user)
// ...
}
import { getEmail } from 'wasp/auth'
export const createTask: CreateTask<...> = async (args, context) => {
const email = getEmail(context.user)
// ...
}
getUsername
The getUsername
helper returns the user's username or null
if the user doesn't have a username auth identity.
- JavaScript
- TypeScript
import { getUsername } from 'wasp/auth'
const MainPage = ({ user }) => {
const username = getUsername(user)
// ...
}
import { getUsername } from 'wasp/auth'
export const createTask = async (args, context) => {
const username = getUsername(context.user)
// ...
}
import { getUsername, AuthUser } from 'wasp/auth'
const MainPage = ({ user }: { user: AuthUser }) => {
const username = getUsername(user)
// ...
}
import { getUsername } from 'wasp/auth'
export const createTask: CreateTask<...> = async (args, context) => {
const username = getUsername(context.user)
// ...
}
getFirstProviderUserId
The getFirstProviderUserId
helper returns the first user ID (e.g. username
or email
) that it finds for the user or null
if it doesn't find any.
As mentioned before, the providerUserId
field is how providers identify our users. For example, the user's username
in the case of the username auth or the user's email
in the case of the email auth. This can be useful if you support multiple authentication methods and you need any ID that identifies the user in your app.
- JavaScript
- TypeScript
import { getFirstProviderUserId } from 'wasp/auth'
const MainPage = ({ user }) => {
const userId = getFirstProviderUserId(user)
// ...
}
import { getFirstProviderUserId } from 'wasp/auth'
export const createTask = async (args, context) => {
const userId = getFirstProviderUserId(context.user)
// ...
}
import { getFirstProviderUserId, AuthUser } from 'wasp/auth'
const MainPage = ({ user }: { user: AuthUser }) => {
const userId = getFirstProviderUserId(user)
// ...
}
import { getFirstProviderUserId } from 'wasp/auth'
export const createTask: CreateTask<...> = async (args, context) => {
const userId = getFirstProviderUserId(context.user)
// ...
}
findUserIdentity
You can find a specific auth identity by using the findUserIdentity
helper function. This function takes a user
and a providerName
and returns the first providerName
identity that it finds or null
if it doesn't find any.
Possible provider names are:
email
username
google
github
This can be useful if you want to check if the user has a specific auth identity. For example, you might want to check if the user has an email auth identity or Google auth identity.
- JavaScript
- TypeScript
import { findUserIdentity } from 'wasp/auth'
const MainPage = ({ user }) => {
const emailIdentity = findUserIdentity(user, 'email')
const googleIdentity = findUserIdentity(user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
import { findUserIdentity } from 'wasp/client/auth'
export const createTask = async (args, context) => {
const emailIdentity = findUserIdentity(context.user, 'email')
const googleIdentity = findUserIdentity(context.user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
import { findUserIdentity, AuthUser } from 'wasp/auth'
const MainPage = ({ user }: { user: AuthUser }) => {
const emailIdentity = findUserIdentity(user, 'email')
const googleIdentity = findUserIdentity(user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
import { findUserIdentity } from 'wasp/client/auth'
export const createTask: CreateTask<...> = async (args, context) => {
const emailIdentity = findUserIdentity(context.user, 'email')
const googleIdentity = findUserIdentity(context.user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
Custom Signup Action
Let's take a look at how you can use the Auth
and AuthIdentity
entities to create custom login and signup actions. For example, you might want to create a custom signup action that creates a user in your app and also creates a user in a third-party service.
In the Email section of the docs we give you an example for custom email signup and in the Username & password section of the docs we give you an example for custom username & password signup.
Below is a simplified version of a custom signup action which you probably wouldn't use in your app but it shows you how you can use the Auth
and AuthIdentity
entities to create a custom signup action.
- JavaScript
- TypeScript
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup.js",
entities: [User]
}
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'
export const signup = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup.js",
entities: [User]
}
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'
import type { CustomSignup } from 'wasp/server/operations'
type CustomSignupInput = {
username: string
password: string
}
type CustomSignupOutput = {
success: boolean
message: string
}
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData<'username'>({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
You can use whichever method suits your needs better: either the createUser
function or Prisma's User.create
method. The createUser
function is a bit more convenient to use because it hides some of the complexity. On the other hand, the User.create
method gives you more control over the data that is stored in the Auth
and AuthIdentity
entities.