--- title: Username & Password --- import { Required } from '@site/src/components/Tag'; import MultipleIdentitiesWarning from './\_multiple-identities-warning.md'; import ReadMoreAboutAuthEntities from './\_read-more-about-auth-entities.md'; import GetUsername from './entities/\_get-username.md'; import UserSignupFieldsExplainer from './\_user-signup-fields-explainer.md'; import UserFieldsExplainer from './\_user-fields.md'; Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client-side. ## Setting Up Username & Password Authentication To set up username authentication we need to: 1. Enable username authentication in the Wasp file 1. Add the `User` entity 1. Add the auth routes and pages 1. Use Auth UI components in our pages Structure of the `main.wasp` file we will end up with: ```wasp title="main.wasp" // Configuring e-mail authentication app myApp { auth: { ... } } // Defining User entity entity User { ... } // Defining routes and pages route SignupRoute { ... } page SignupPage { ... } // ... ``` ### 1. Enable Username Authentication Let's start with adding the following to our `main.wasp` file: ```wasp title="main.wasp" {11} app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { // 1. Specify the user entity (we'll define it next) userEntity: User, methods: { // 2. Enable username authentication usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login" } } ``` ```wasp title="main.wasp" {11} app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { // 1. Specify the user entity (we'll define it next) userEntity: User, methods: { // 2. Enable username authentication usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login" } } ``` Read more about the `usernameAndPassword` auth method options [here](#fields-in-the-usernameandpassword-dict). ### 2. Add the User Entity The `User` entity can be as simple as including only the `id` field: ```wasp title="main.wasp" // 3. Define the user entity entity User {=psl // highlight-next-line id Int @id @default(autoincrement()) // Add your own fields below // ... psl=} ``` ```wasp title="main.wasp" // 3. Define the user entity entity User {=psl // highlight-next-line id Int @id @default(autoincrement()) // Add your own fields below // ... psl=} ``` ### 3. Add the Routes and Pages Next, we need to define the routes and pages for the authentication pages. Add the following to the `main.wasp` file: ```wasp title="main.wasp" // ... // 4. Define the routes route LoginRoute { path: "/login", to: LoginPage } page LoginPage { component: import { Login } from "@src/pages/auth.jsx" } route SignupRoute { path: "/signup", to: SignupPage } page SignupPage { component: import { Signup } from "@src/pages/auth.jsx" } ``` ```wasp title="main.wasp" // ... // 4. Define the routes route LoginRoute { path: "/login", to: LoginPage } page LoginPage { component: import { Login } from "@src/pages/auth.tsx" } route SignupRoute { path: "/signup", to: SignupPage } page SignupPage { component: import { Signup } from "@src/pages/auth.tsx" } ``` We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below. ### 4. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../project/css-frameworks). ::: Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.jsx" import { LoginForm, SignupForm } from 'wasp/client/auth' import { Link } from 'react-router-dom' export function Login() { return (
Don't have an account yet? go to signup.
); } export function Signup() { return (
I already have an account (go to login).
); } // A layout component to center the content export function Layout({ children }) { return (
{children}
); } ```
```tsx title="src/pages/auth.tsx" import { LoginForm, SignupForm } from 'wasp/client/auth' import { Link } from 'react-router-dom' export function Login() { return (
Don't have an account yet? go to signup.
); } export function Signup() { return (
I already have an account (go to login).
); } // A layout component to center the content export function Layout({ children }: { children: React.ReactNode }) { return (
{children}
); } ```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../auth/ui). ### Conclusion That's it! We have set up username authentication in our app. 🎉 Running `wasp db migrate-dev` and then `wasp start` should give you a working app with username authentication. If you want to put some of the pages behind authentication, read the [auth overview docs](../auth/overview). ## Customizing the Auth Flow The login and signup flows are pretty standard: they allow the user to sign up and then log in with their username and password. The signup flow validates the username and password and then creates a new user entity in the database. Read more about the default username and password validation rules in the [auth overview docs](../auth/overview#default-validations). If you require more control in your authentication flow, you can achieve that in the following ways: 1. Create your UI and use `signup` and `login` actions. 1. Create your custom sign-up action which uses the lower-level API, along with your custom code. ### 1. Using the `signup` and `login` actions #### `login()` An action for logging in the user. It takes two arguments: - `username: string` Username of the user logging in. - `password: string` Password of the user logging in. You can use it like this: ```jsx title="src/pages/auth.jsx" import { login } from 'wasp/client/auth' import { useState } from 'react' import { useHistory, Link } from 'react-router-dom' export function LoginPage() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const history = useHistory() async function handleSubmit(event) { event.preventDefault() try { await login(username, password) history.push('/') } catch (error) { setError(error) } } return (
{/* ... */}
); } ```
```tsx title="src/pages/auth.tsx" import { login } from 'wasp/client/auth' import { useState } from 'react' import { useHistory, Link } from 'react-router-dom' export function LoginPage() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const history = useHistory() async function handleSubmit(event: React.FormEvent) { event.preventDefault() try { await login(username, password) history.push('/') } catch (error: unknown) { setError(error as Error) } } return (
{/* ... */}
); } ```
:::note When using the exposed `login()` function, make sure to implement your redirect on success login logic (e.g. redirecting to home). ::: #### `signup()` An action for signing up the user. This action does not log in the user, you still need to call `login()`. It takes one argument: - `userFields: object` It has the following fields: - `username: string` - `password: string` :::info By default, Wasp will only save the `username` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](../auth/overview#customizing-the-signup-process). ::: You can use it like this: ```jsx title="src/pages/auth.jsx" import { signup, login } from 'wasp/client/auth' import { useState } from 'react' import { useHistory } from 'react-router-dom' import { Link } from 'react-router-dom' export function Signup() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const history = useHistory() async function handleSubmit(event) { event.preventDefault() try { await signup({ username, password, }) await login(username, password) history.push("/") } catch (error) { setError(error) } } return (
{/* ... */}
); } ```
```tsx title="src/pages/auth.tsx" import { signup, login } from 'wasp/client/auth' import { useState } from 'react' import { useHistory } from 'react-router-dom' import { Link } from 'react-router-dom' export function Signup() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const history = useHistory() async function handleSubmit(event: React.FormEvent) { event.preventDefault() try { await signup({ username, password, }) await login(username, password) history.push("/") } catch (error: unknown) { setError(error as Error) } } return (
{/* ... */}
); } ```
### 2. Creating your custom sign-up action The code of your custom sign-up action can look like this: ```wasp title="main.wasp" // ... action customSignup { fn: import { signup } from "@src/auth/signup.js", } ``` ```js title="src/auth/signup.js" import { ensurePasswordIsPresent, ensureValidPassword, ensureValidUsername, createProviderId, sanitizeAndSerializeProviderData, createUser, } from 'wasp/server/auth' export const signup = async (args, _context) => { ensureValidUsername(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId = createProviderId('username', args.username) const providerData = await sanitizeAndSerializeProviderData({ hashedPassword: args.password, }) await createUser( providerId, providerData, // Any additional data you want to store on the User entity {}, ) } catch (e) { return { success: false, message: e.message, } } // Your custom code after sign-up. // ... return { success: true, message: 'User created successfully', } } ``` ```wasp title="main.wasp" // ... action customSignup { fn: import { signup } from "@src/auth/signup.js", } ``` ```ts title="src/auth/signup.ts" import { ensurePasswordIsPresent, ensureValidPassword, ensureValidUsername, 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, _context) => { ensureValidUsername(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId = createProviderId('username', args.username) const providerData = await sanitizeAndSerializeProviderData<'username'>({ hashedPassword: args.password, }) await createUser( providerId, providerData, // Any additional data you want to store on the User entity {}, ) } catch (e) { return { success: false, message: e.message, } } // Your custom code after sign-up. // ... return { success: true, message: 'User created successfully', } } ``` We suggest using the built-in field validators for your authentication flow. You can import them from `wasp/server/auth`. These are the same validators that Wasp uses internally for the default authentication flow. #### Username - `ensureValidUsername(args)` Checks if the username is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations). #### Password - `ensurePasswordIsPresent(args)` Checks if the password is present and throws an error if it's not. - `ensureValidPassword(args)` Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations). ## Using Auth To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](../auth/overview). ### `getUsername` If you are looking to access the user's username in your code, you can do that by accessing the info about the user that is stored in the `user.auth.identities` array. To make things a bit easier for you, Wasp offers the `getUsername` helper. ## API Reference ### `userEntity` fields ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login" } } entity User {=psl id Int @id @default(autoincrement()) psl=} ``` ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login" } } entity User {=psl id Int @id @default(autoincrement()) psl=} ``` ### Fields in the `usernameAndPassword` dict ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { usernameAndPassword: { userSignupFields: import { userSignupFields } from "@src/auth/email.js", }, }, onAuthFailedRedirectTo: "/login" } } // ... ``` ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { usernameAndPassword: { userSignupFields: import { userSignupFields } from "@src/auth/email.js", }, }, onAuthFailedRedirectTo: "/login" } } // ... ``` #### `userSignupFields: ExtImport` Read more about the `userSignupFields` function [here](./overview#1-defining-extra-fields).