Username & Password
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:
- Enable username authentication in the Wasp file
- Add the user entity
- Add the routes and pages
- Use Auth UI components in our pages
Structure of the main.wasp file we will end up with:
// 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:
- JavaScript
- TypeScript
app myApp {
  wasp: {
    version: "^0.11.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"
  }
}
app myApp {
  wasp: {
    version: "^0.11.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.
2. Add the User Entity
When username authentication is enabled, Wasp expects certain fields in your userEntity. Let's add these fields to our main.wasp file:
- JavaScript
- TypeScript
// 3. Define the user entity
entity User {=psl
    id                        Int           @id @default(autoincrement())
    username                  String        @unique
    password                  String
    // Add your own fields below
    // ...
psl=}
// 3. Define the user entity
entity User {=psl
    id                        Int           @id @default(autoincrement())
    username                  String        @unique
    password                  String
    // Add your own fields below
    // ...
psl=}
Read more about the userEntity fields here.
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:
- JavaScript
- TypeScript
// ...
// 4. Define the routes
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
  component: import { Login } from "@client/pages/auth.jsx"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
  component: import { Signup } from "@client/pages/auth.jsx"
}
// ...
// 4. Define the routes
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
  component: import { Login } from "@client/pages/auth.tsx"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
  component: import { Signup } from "@client/pages/auth.tsx"
}
We'll define the React components for these pages in the client/pages/auth.tsx file below.
4. Create the Client Pages
We are using Tailwind CSS to style the pages. Read more about how to add it here.
Let's create a auth.tsx file in the client/pages folder and add the following to it:
- JavaScript
- TypeScript
import { LoginForm } from "@wasp/auth/forms/Login";
import { SignupForm } from "@wasp/auth/forms/Signup";
import { Link } from "react-router-dom";
export function Login() {
  return (
    <Layout>
      <LoginForm />
      <br />
      <span className="text-sm font-medium text-gray-900">
        Don't have an account yet? <Link to="/signup">go to signup</Link>.
      </span>
    </Layout>
  );
}
export function Signup() {
  return (
    <Layout>
      <SignupForm />
      <br />
      <span className="text-sm font-medium text-gray-900">
        I already have an account (<Link to="/login">go to login</Link>).
      </span>
    </Layout>
  );
}
// A layout component to center the content
export function Layout({ children }) {
  return (
    <div className="w-full h-full bg-white">
      <div className="min-w-full min-h-[75vh] flex items-center justify-center">
        <div className="w-full h-full max-w-sm p-5 bg-white">
          <div>{children}</div>
        </div>
      </div>
    </div>
  );
}
import { LoginForm } from "@wasp/auth/forms/Login";
import { SignupForm } from "@wasp/auth/forms/Signup";
import { Link } from "react-router-dom";
export function Login() {
  return (
    <Layout>
      <LoginForm />
      <br />
      <span className="text-sm font-medium text-gray-900">
        Don't have an account yet? <Link to="/signup">go to signup</Link>.
      </span>
    </Layout>
  );
}
export function Signup() {
  return (
    <Layout>
      <SignupForm />
      <br />
      <span className="text-sm font-medium text-gray-900">
        I already have an account (<Link to="/login">go to login</Link>).
      </span>
    </Layout>
  );
}
// A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div className="w-full h-full bg-white">
      <div className="min-w-full min-h-[75vh] flex items-center justify-center">
        <div className="w-full h-full max-w-sm p-5 bg-white">
          <div>{children}</div>
        </div>
      </div>
    </div>
  );
}
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components here.
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 using auth docs.
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 and how to override them in the using auth docs.
If you require more control in your authentication flow, you can achieve that in the following ways:
- Create your UI and use signupandloginactions.
- Create your custom sign-up and login actions which uses the Prisma client, 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: stringrequired
Username of the user logging in.
- password: stringrequired
Password of the user logging in.
You can use it like this:
- JavaScript
- TypeScript
// Importing the login action 👇
import login from '@wasp/auth/login'
import { useState } from 'react'
import { useHistory } from 'react-router-dom'
import { 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 (
    <form onSubmit={handleSubmit}>
      {/* ... */}
    </form>
  );
}
// Importing the login action 👇
import login from '@wasp/auth/login'
import { useState } from 'react'
import { useHistory } from 'react-router-dom'
import { Link } from 'react-router-dom'
export function LoginPage() {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState<Error | null>(null)
  const history = useHistory()
  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    try {
      await login(username, password)
      history.push('/')
    } catch (error: unknown) {
      setError(error as Error)
    }
  }
  return (
    <form onSubmit={handleSubmit}>
      {/* ... */}
    </form>
  );
}
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: objectrequiredIt has the following fields: - 
username: stringrequired
- 
password: stringrequired
 infoWasp only stores the auth-related fields of the user entity. Adding extra fields to userFieldswill not have any effect.If you need to add extra fields to the user entity, we suggest doing it in a separate step after the user logs in for the first time. 
- 
You can use it like this:
- JavaScript
- TypeScript
// Importing the signup and login actions 👇
import signup from '@wasp/auth/signup'
import login from '@wasp/auth/login'
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 (
    <form onSubmit={handleSubmit}>
      {/* ... */}
    </form>
  );
}
// Importing the signup and login actions 👇
import signup from '@wasp/auth/signup'
import login from '@wasp/auth/login'
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<Error | null>(null)
  const history = useHistory()
  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    try {
      await signup({
        username,
        password,
      })
      await login(username, password)
      history.push("/")
    } catch (error: unknown) {
      setError(error as Error)
    }
  }
  return (
    <form onSubmit={handleSubmit}>
      {/* ... */}
    </form>
  );
}
2. Creating your custom actions
The code of your custom sign-up action can look like this:
- JavaScript
- TypeScript
// ...
action signupUser {
  fn: import { signUp } from "@server/auth/signup.js",
  entities: [User]
}
export const signUp = async (args, context) => {
  // Your custom code before sign-up.
  // ...
  const newUser = context.entities.User.create({
    data: {
      username: args.username,
      password: args.password // password hashed automatically by Wasp! 🐝
    }
  })
  // Your custom code after sign-up.
  // ...
  return newUser
}
// ...
action signupUser {
  fn: import { signUp } from "@server/auth/signup.js",
  entities: [User]
}
import type { User } from '@wasp/entities'
import type { SignupUser } from '@wasp/actions/types'
type SignupPayload = Pick<User, 'username' | 'password'>
export const signUp: SignupUser<SignupPayload, User> = async (args, context) => {
  // Your custom code before sign-up.
  // ...
  const newUser = context.entities.User.create({
    data: {
      username: args.username,
      password: args.password // password hashed automatically by Wasp! 🐝
    }
  })
  // Your custom code after sign-up.
  // ...
  return newUser
}
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 using auth docs.
API Reference
userEntity fields
- JavaScript
- TypeScript
app myApp {
  wasp: {
    version: "^0.11.0"
  },
  title: "My App",
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {},
    },
    onAuthFailedRedirectTo: "/login"
  }
}
// Wasp requires the `userEntity` to have at least the following fields
entity User {=psl
    id                        Int           @id @default(autoincrement())
    username                  String        @unique
    password                  String
psl=}
app myApp {
  wasp: {
    version: "^0.11.0"
  },
  title: "My App",
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {},
    },
    onAuthFailedRedirectTo: "/login"
  }
}
// Wasp requires the `userEntity` to have at least the following fields
entity User {=psl
    id                        Int           @id @default(autoincrement())
    username                  String        @unique
    password                  String
psl=}
Username & password auth requires that userEntity specified in auth contains:
- usernamefield of type- String
- passwordfield of type- String
Fields in the usernameAndPassword dict
- JavaScript
- TypeScript
app myApp {
  wasp: {
    version: "^0.11.0"
  },
  title: "My App",
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {},
    },
    onAuthFailedRedirectTo: "/login"
  }
}
// ...
app myApp {
  wasp: {
    version: "^0.11.0"
  },
  title: "My App",
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {},
    },
    onAuthFailedRedirectTo: "/login"
  }
}
// ...
usernameAndPassword dict doesn't have any options at the moment.
You can read about the rest of the auth options in the using auth section of the docs.