Skip to main content
Version: 2.x

Other Enhancements

Besides core enhancements like access policies, ZenStack also provides a few other lightweight enhancements targeting specific use cases.

Hashing Passwords

When using a credential-based authentication system, it's important never to store the password in plain text. ZenStack provides a simple way to automatically hash passwords before storing them in the database. To enable it, simply mark the password field with the @password attribute.

model User {
id Int @id @default(autoincrement())
email String @unique
password String? @password
}

Under the hood, ZenStack uses bcryptjs to generate a hash.

See here for more details about the @password attribute.

🛠️ Adding User Password

Let's add a password field to our User model, so we can implement credential-based authentication in the future.

model User {
...
password String? @password
}

Rerun generation, push database schema, and start REPL:

npx zenstack generate
npx prisma db push
npx zenstack repl

Try to create a new user with a password:

db.user.create({ data: { email: 'ross@zenstack.dev', password: 'abc123' }})

You may see a surprising error:

denied by policy: user entities failed 'create' check, result is not allowed to be read back
Code: P2004
Meta: { reason: 'RESULT_NOT_READABLE' }

Recall that in previous chapters, we mentioned that "write can imply read". In this case, the create operation returns the created user object, which is subject to the "read" policy check. Since we're using an anonymous context, the read operation is denied.

However, if you use the raw Prisma Client to query, you can see the user is created, and his password is hashed:

prisma.user.findFirst({ orderBy: { id: 'desc' } });
{
id: 3,
createdAt: 2023-11-09T05:53:28.793Z,
updatedAt: 2023-11-09T05:53:28.793Z,
email: 'ross@zenstack.dev',
password: '$2a$12$jYdALEg7gtIi.tc9JmFKuOG3X//0Cdo801xYotdtUw5pXV6Ahb.2m',
name: null
}
info

This "result not readable" issue is often not a problem in practice, since a user with write privilege can also usually read. Sign-up flow is a special case where a user transitions from anonymous to authenticated. You can catch and ignore the error in the sign-up part. After the user logs in, you can use his identity for the subsequent operations.

Or alternatively, you can use the raw Prisma Client in the authentication part of your system.

Omitting Fields

Some database fields can be sensitive and should not be exposed to the client. Password is a good example (even when it's hashed). You can use the @omit attribute to mark a field, and it'll be automatically omitted when queried from an enhanced Prisma Client.

model User {
...
password String? @password @omit
}
info

You can use a field-level access policy to achieve the same goal:

model User {
...
password String? @password @allow('read', false)
}

However, using @omit is more appropriate since conceptually omitting sensitive field is not a permission issue.

🛠️ Omitting Password Field

Let's mark the password field with @omit:

model User {
...
password String? @password @omit
}

Rerun generation, push database schema, and start REPL:

npx zenstack generate
npx prisma db push
npx zenstack repl

Query users, and you'll find the password field is omitted:

.auth { id: 1 }
db.user.findFirst();
{
id: 1,
createdAt: 2023-11-07T21:37:22.506Z,
updatedAt: 2023-11-07T21:37:22.506Z,
email: 'joey@zenstack.dev',
name: 'Joey'
}
Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below