--- title: Using Auth --- import { AuthMethodsGrid } from "@site/src/components/AuthMethodsGrid"; import { Required } from "@site/src/components/Required"; Auth is an essential piece of any serious application. Coincidentally, Wasp provides authentication and authorization support out of the box 🙃. Enabling auth for your app is optional and can be done by configuring the `auth` field of the `app` declaration. ```wasp title="main.wasp" app MyApp { title: "My app", //... auth: { userEntity: User, externalAuthEntity: SocialLogin, methods: { usernameAndPassword: {}, // use this or email, not both email: {}, // use this or usernameAndPassword, not both google: {}, gitHub: {}, }, onAuthFailedRedirectTo: "/someRoute" } } //... ``` ```wasp title="main.wasp" app MyApp { title: "My app", //... auth: { userEntity: User, externalAuthEntity: SocialLogin, methods: { usernameAndPassword: {}, // use this or email, not both email: {}, // use this or usernameAndPassword, not both google: {}, gitHub: {}, }, onAuthFailedRedirectTo: "/someRoute" } } //... ``` Read more about the `auth` field options in the [API Reference](#api-reference) section. We will provide a quick overview of auth in Wasp and link to more detailed documentation for each auth method. ## Available auth methods Wasp supports the following auth methods: Let's say we enabled the [Username & password](/docs/auth/username-and-pass) authentication. We get an auth backend with signup and login endpoints. We also get the `user` object in our [Operations](/docs/data-model/operations/overview) and we can decide what to do based on whether the user is logged in or not. We would also get the [Auth UI](/docs/auth/ui) generated for us. We can set up our login and signup pages where our users can **create their account** and **login**. We can then protect certain pages by setting `authRequired: true` for them. This will make sure that only logged-in users can access them. We will also have access to the `user` object in our frontend code, so we can show different UI to logged-in and logged-out users. For example, we can show the user's name in the header alongside a **logout button** or a login button if the user is not logged in. ## Protecting a page with `authRequired` When declaring a page, you can set the `authRequired` property. If you set it to `true`, only authenticated users can access the page. Unauthenticated users are redirected to a route defined by the `app.auth.onAuthFailedRedirectTo` field. ```wasp title="main.wasp" page MainPage { component: import Main from "@client/pages/Main.jsx", authRequired: true } ``` ```wasp title="main.wasp" page MainPage { component: import Main from "@client/pages/Main.tsx", authRequired: true } ``` :::caution Requires auth method You can only use `authRequired` if your app uses one of the [available auth methods](#available-auth-methods). ::: If `authRequired` is set to `true`, the page's React component (specified by the `component` property) receives the `user` object as a prop. Read more about the `user` object in the [Accessing the logged-in user section](#accessing-the-logged-in-user). ## Logout action We provide an action for logging out the user. Here's how you can use it: ```jsx title="client/components/LogoutButton.jsx" import logout from '@wasp/auth/logout' const LogoutButton = () => { return ( ) } ``` ```tsx title="client/components/LogoutButton.tsx" import logout from '@wasp/auth/logout' const LogoutButton = () => { return ( ) } ``` ## Accessing the logged-in user You can get access to the `user` object both in the backend and on the frontend. ### On the client There are two ways to access the `user` object on the client: - the `user` prop - the `useAuth` hook #### Using the `user` prop If the page's declaration sets `authRequired` to `true`, the page's React component receives the `user` object as a prop: ```wasp title="main.wasp" // ... page AccountPage { component: import Account from "@client/pages/Account.jsx", authRequired: true } ``` ```jsx title="client/pages/Account.jsx" import Button from './Button'; import logout from '@wasp/auth/logout'; const AccountPage = ({ user }) => { return (
{JSON.stringify(user, null, 2)}
); }; export default AccountPage; ```
```wasp title="main.wasp" // ... page AccountPage { component: import Account from "@client/pages/Account.tsx", authRequired: true } ``` ```tsx title="client/pages/Account.tsx" import type { User } from '@wasp/entities'; import Button from './Button'; import logout from '@wasp/auth/logout'; const AccountPage = ({ user }: { user: User }) => { return (
{JSON.stringify(user, null, 2)}
); }; export default AccountPage; ```
#### Using the `useAuth` hook Wasp provides a React hook you can use in the client components - `useAuth`. This hook is a thin wrapper over Wasp's `useQuery` hook and returns data in the same format. ```jsx title="src/client/pages/MainPage.jsx" import useAuth from '@wasp/auth/useAuth' import { Link } from 'react-router-dom' import logout from '@wasp/auth/logout' import Todo from '../Todo' export function Main() { const { data: user } = useAuth() if (!user) { return ( Please login or sign up. ) } else { return ( <> < /> ) } } ``` ```tsx title="src/client/pages/MainPage.tsx" import useAuth from '@wasp/auth/useAuth' import { Link } from 'react-router-dom' import logout from '@wasp/auth/logout' import Todo from '../Todo' export function Main() { const { data: user } = useAuth() if (!user) { return ( Please login or sign up. ) } else { return ( <> < /> ) } } ``` :::tip Since the `user` prop is only available in a page's React component: use the `user` prop in the page's React component and the `useAuth` hook in any other React component. ::: ### On the server #### Using the `context.user` object When authentication is enabled, all [queries and actions](/docs/data-model/operations/overview) have access to the `user` object through the `context` argument. `context.user` contains all User entity's fields, except for the password. ```js title="src/server/actions.js" import HttpError from '@wasp/core/HttpError.js' export const createTask = async (task, context) => { if (!context.user) { throw new HttpError(403) } const Task = context.entities.Task return Task.create({ data: { description: task.description, user: { connect: { id: context.user.id } } } }) } ``` ```ts title="src/server/actions.ts" import type { Task } from "@wasp/entities" import type { CreateTask } from "@wasp/actions/types" import HttpError from '@wasp/core/HttpError.js' type CreateTaskPayload = Pick export const createTask: CreateTask = async (args, context) => { if (!context.user) { throw new HttpError(403) } const Task = context.entities.Task return Task.create({ data: { description: args.description, user: { connect: { id: context.user.id } } } }) } ``` To implement access control in your app, each operation must check `context.user` and decide what to do. For example, if `context.user` is `undefined` inside a private operation, the user's access should be denied. When using WebSockets, the `user` object is also available on the `socket.data` object. Read more in the [WebSockets section](/docs/advanced/web-sockets#websocketfn-function). ## User entity ### Password hashing You don't need to worry about hashing the password yourself. Even when directly using the Prisma client and calling `create()` with a plain-text password, Wasp's middleware makes sure to hash the password before storing it in the database. For example, if you need to update a user's password, you can safely use the Prisma client to do so, e.g., inside an Action: ```js title="src/server/actions.js" export const updatePassword = async (args, context) => { return context.entities.User.update({ where: { id: args.userId }, data: { password: 'New pwd which will be hashed automatically!' } }) } ``` ```ts title="src/server/actions.ts" import type { UpdatePassword } from "@wasp/actions/types" import type { User } from "@wasp/entities" type UpdatePasswordPayload = { userId: User["id"] } export const updatePassword: UpdatePassword = async (args, context) => { return context.entities.User.update({ where: { id: args.userId }, data: { password: 'New pwd which will be hashed automatically!' } }) } ``` ### Default validations Wasp includes several basic validation mechanisms. If you need something extra, the [next section](#customizing-validations) shows how to customize them. Default validations depend on the auth method you use. #### Username & password If you use [Username & password](/docs/auth/username-and-pass) authentication, the default validations are: - The `username` must not be empty - The `password` must not be empty, have at least 8 characters, and contain a number Note that `username`s are stored in a **case-sensitive** manner. #### Email If you use [Email](/docs/auth/email) authentication, the default validations are: - The `email` must not be empty and a valid email address - The `password` must not be empty, have at least 8 characters, and contain a number Note that `email`s are stored in a **case-insensitive** manner. ### Customizing validations :::note You can only disable the default validation for **Username & password** authentication, but you can add custom validations can to both **Username & password** and **Email** auth methods. This is a bug in Wasp that is being tracked [here](https://github.com/wasp-lang/wasp/issues/1358) ::: To disable/enable default validations, or add your own, modify your custom signup function: ```js const newUser = context.entities.User.create({ data: { username: args.username, password: args.password // password hashed automatically by Wasp! 🐝 }, _waspSkipDefaultValidations: false, // can be omitted if false (default), or explicitly set to true _waspCustomValidations: [ { validates: 'password', message: 'password must contain an uppercase letter', validator: password => /[A-Z]/.test(password) }, ] }) ``` ```ts const newUser = context.entities.User.create({ data: { username: args.username, password: args.password // password hashed automatically by Wasp! 🐝 }, _waspSkipDefaultValidations: false, // can be omitted if false (default), or explicitly set to true _waspCustomValidations: [ { validates: 'password', message: 'password must contain an uppercase letter', validator: password => /[A-Z]/.test(password) }, ] }) ``` :::info Validations always run on `create()`. For `update()`, they only run when the field mentioned in `validates` is present. The validation process stops on the first `validator` to return false. If enabled, default validations run first and then custom validations. ::: ### Validation Error Handling When creating, updating, or deleting entities, you may wish to handle validation errors. Wasp exposes a class called `AuthError` for this purpose. ```js title="src/server/actions.js" try { await context.entities.User.update(...) } catch (e) { if (e instanceof AuthError) { throw new HttpError(422, 'Validation failed', { message: e.message }) } else { throw e } } ``` ```ts title="src/server/actions.ts" try { await context.entities.User.update(...) } catch (e) { if (e instanceof AuthError) { throw new HttpError(422, 'Validation failed', { message: e.message }) } else { throw e } } ``` ## API Reference ```wasp title="main.wasp" title: "My app", //... auth: { userEntity: User, externalAuthEntity: SocialLogin, methods: { usernameAndPassword: {}, // use this or email, not both email: {}, // use this or usernameAndPassword, not both google: {}, gitHub: {}, }, onAuthFailedRedirectTo: "/someRoute" } } //... ``` ```wasp title="main.wasp" app MyApp { title: "My app", //... auth: { userEntity: User, externalAuthEntity: SocialLogin, methods: { usernameAndPassword: {}, // use this or email, not both email: {}, // use this or usernameAndPassword, not both google: {}, gitHub: {}, }, onAuthFailedRedirectTo: "/someRoute" } } //... ``` `app.auth` is a dictionary with the following fields: #### `userEntity: entity` The entity representing the user. Its mandatory fields depend on your chosen auth method. #### `externalAuthEntity: entity` Wasp requires you to set the field `auth.externalAuthEntity` for all authentication methods relying on an external authorizatino provider (e.g., Google). You also need to tweak the Entity referenced by `auth.userEntity`, as shown below. ```wasp {4,14} title="main.wasp" //... auth: { userEntity: User, externalAuthEntity: SocialLogin, //... entity User {=psl id Int @id @default(autoincrement()) //... externalAuthAssociations SocialLogin[] psl=} entity SocialLogin {=psl id Int @id @default(autoincrement()) provider String providerId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int createdAt DateTime @default(now()) @@unique([provider, providerId, userId]) psl=} ``` ```wasp {4,14} title="main.wasp" //... auth: { userEntity: User, externalAuthEntity: SocialLogin, //... entity User {=psl id Int @id @default(autoincrement()) //... externalAuthAssociations SocialLogin[] psl=} entity SocialLogin {=psl id Int @id @default(autoincrement()) provider String providerId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int createdAt DateTime @default(now()) @@unique([provider, providerId, userId]) psl=} ``` :::note The same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity). ::: See [Google docs](/docs/auth/social-auth/google) and [GitHub docs](/docs/auth/social-auth/github) for more details. #### `methods: dict` A dictionary of auth methods enabled for the app. #### `onAuthFailedRedirectTo: String` The route to which Wasp should redirect unauthenticated user when they try to access a private page (i.e., a page that has `authRequired: true`). Check out these [essentials docs on auth](/docs/tutorial/auth#adding-auth-to-the-project) to see an example of usage. #### `onAuthSucceededRedirectTo: String` The route to which Wasp will send a successfully authenticated after a successful login/signup. The default value is `"/"`. :::note Automatic redirect on successful login only works when using the Wasp-provided [Auth UI](/docs/auth/ui). :::