Add e-mail auth docs (#1113)
@ -2,6 +2,57 @@
|
||||
|
||||
## v0.10.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- we changed `LoginForm` and `SignupForm` to use a named export instead of a default export, this means you must use them like this:
|
||||
- `import { LoginForm } from '@wasp/auth/forms/Login'`
|
||||
- `import { SignupForm } from '@wasp/auth/Signup'`
|
||||
- we renamed `useAuth.js` to `useAuth.ts` and you should import it like this: `import useAuth from '@wasp/auth/useAuth'` (without the `.js` extension)
|
||||
|
||||
### Adds support for e-mail authentication
|
||||
|
||||
You can now use e-mail authentication in your Wasp app! This means that users can sign up and log in using their e-mail address. You get e-mail verification and password reset out of the box.
|
||||
|
||||
```c
|
||||
app MyApp {
|
||||
// ...
|
||||
auth: {
|
||||
// ...
|
||||
email: {
|
||||
fromField: {
|
||||
name: "ToDO App",
|
||||
email: "mihovil@ilakovac.com"
|
||||
},
|
||||
emailVerification: {
|
||||
allowUnverifiedLogin: false,
|
||||
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
|
||||
clientRoute: EmailVerificationRoute,
|
||||
},
|
||||
passwordReset: {
|
||||
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
|
||||
clientRoute: PasswordResetRoute
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can only use one of e-mail or username & password authentication in your app. You can't use both at the same time.
|
||||
|
||||
### Adds Auth UI components
|
||||
|
||||
Wasp now provides a set of UI components for authentication. You can use them to quickly build a login and signup page for your app. The UI changes dynamically based on your Wasp config.
|
||||
|
||||
We provide `LoginForm`, `SignupForm`, `ForgotPassworForm`, `ResetPasswordForm` and`VerifyEmailForm` components. You can import them from `@wasp/auth/forms` like:
|
||||
|
||||
```js
|
||||
import { LoginForm } from '@wasp/auth/forms/Login'
|
||||
import { SignupForm } from '@wasp/auth/forms/Signup'
|
||||
import { ForgotPasswordForm } from '@wasp/auth/forms/ForgotPassword'
|
||||
import { ResetPasswordForm } from '@wasp/auth/forms/ResetPassword'
|
||||
import { VerifyEmailForm } from '@wasp/auth/forms/VerifyEmail'
|
||||
```
|
||||
|
||||
### Adds support for database seeding
|
||||
You can now define JS/TS functions for seeding the database!
|
||||
|
||||
|
430
web/docs/guides/email-auth.md
Normal file
@ -0,0 +1,430 @@
|
||||
---
|
||||
title: Email Authentication
|
||||
---
|
||||
|
||||
# Email Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Wasp supports e-mail authentication out of the box, along with email verification and "forgot your password?" flows. It provides a set of routes and email templates that you can use to implement it in your app.
|
||||
|
||||
![Auth UI](/img/authui/all_screens.gif)
|
||||
|
||||
In this guide, we'll go through the easiest way to set up email authentication: using Wasp's Auth UI components.
|
||||
|
||||
## Outline of the guide
|
||||
|
||||
We'll need to take the following steps to set up email authentication:
|
||||
- [ ] Set up email authentication in `main.wasp`
|
||||
- [ ] Add the user entity
|
||||
- [ ] Add the routes and pages
|
||||
- [ ] Set up the email sender in `main.wasp` and `.env.server`
|
||||
- [ ] Use Auth UI components in our pages
|
||||
|
||||
Outline of the Wasp file we'll be working with:
|
||||
|
||||
```c title="main.wasp"
|
||||
// Configuring e-mail authentication
|
||||
app myApp { ... }
|
||||
|
||||
// Defining User entity
|
||||
entity User { ... }
|
||||
|
||||
// Defining routes and pages
|
||||
route SignupRoute { ... }
|
||||
page SignupPage { ... }
|
||||
// ...
|
||||
```
|
||||
|
||||
### Email authentication in `main.wasp`
|
||||
|
||||
Let's first set up the email authentication by adding the following to our `main.wasp` file:
|
||||
|
||||
```c title="main.wasp"
|
||||
app myApp {
|
||||
wasp: {
|
||||
version: "^0.10.0"
|
||||
},
|
||||
title: "My App",
|
||||
auth: {
|
||||
// 1. Specify the user entity
|
||||
userEntity: User,
|
||||
methods: {
|
||||
// 2. Enable email authentication
|
||||
email: {
|
||||
// 3. Specify the email from field
|
||||
fromField: {
|
||||
name: "My App Postman",
|
||||
email: "hello@itsme.com"
|
||||
},
|
||||
// 4. Specify the email verification and password reset options
|
||||
emailVerification: {
|
||||
clientRoute: EmailVerificationRoute,
|
||||
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
|
||||
allowUnverifiedLogin: false,
|
||||
},
|
||||
passwordReset: {
|
||||
clientRoute: PasswordResetRoute,
|
||||
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login",
|
||||
onAuthSucceededRedirectTo: "/profile"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### User entity
|
||||
|
||||
Then we'll define the `User` entity in our `main.wasp` file:
|
||||
|
||||
```c title="main.wasp" {4-8}
|
||||
// 5. Define the user entity
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
password String?
|
||||
isEmailVerified Boolean @default(false)
|
||||
emailVerificationSentAt DateTime?
|
||||
passwordResetSentAt DateTime?
|
||||
// Add your own fields below
|
||||
// ...
|
||||
psl=}
|
||||
```
|
||||
|
||||
### Routes and pages
|
||||
|
||||
Next, we need to define the routes and pages for the authentication pages. We'll show the React code later, but for now we'll just define the routes and pages.
|
||||
|
||||
We'll add the following to our `main.wasp` file:
|
||||
|
||||
```c title="main.wasp"
|
||||
// 6. Define the routes
|
||||
route SignupRoute { path: "/signup", to: SignupPage }
|
||||
page SignupPage {
|
||||
component: import { Signup } from "@client/pages/auth/Signup.tsx"
|
||||
}
|
||||
|
||||
route LoginRoute { path: "/login", to: LoginPage }
|
||||
page LoginPage {
|
||||
component: import { Login } from "@client/pages/auth/Login.tsx"
|
||||
}
|
||||
|
||||
route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
|
||||
page RequestPasswordResetPage {
|
||||
component: import { RequestPasswordReset } from "@client/pages/auth/RequestPasswordReset.tsx",
|
||||
}
|
||||
|
||||
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
|
||||
page PasswordResetPage {
|
||||
component: import { PasswordReset } from "@client/pages/auth/PasswordReset.tsx",
|
||||
}
|
||||
|
||||
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
|
||||
page EmailVerificationPage {
|
||||
component: import { EmailVerification } from "@client/pages/auth/EmailVerification.tsx",
|
||||
}
|
||||
```
|
||||
|
||||
### Email sender
|
||||
|
||||
We'll use SendGrid in this guide to send our e-mails. You can use any of the supported email providers. Read more about setting up the email sender in the [email sender setup guide](/docs/guides/sending-emails).
|
||||
|
||||
To set up SendGrid to send emails, we will add the following to our `main.wasp` file:
|
||||
|
||||
```c title="main.wasp"
|
||||
app myApp {
|
||||
...
|
||||
emailSender: {
|
||||
provider: SendGrid,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
... and add the following to our `.env.server` file:
|
||||
|
||||
```c title=".env.server"
|
||||
SENDGRID_API_KEY=<your key>
|
||||
```
|
||||
|
||||
## Using Auth UI
|
||||
|
||||
:::info
|
||||
We are using [Tailwind CSS](https://tailwindcss.com/) to style the page. Read more about how to add it [here](/docs/integrations/css-frameworks#tailwind).
|
||||
:::
|
||||
|
||||
### Signup page
|
||||
|
||||
![Auth UI](/img/authui/signup.png)
|
||||
|
||||
We are using the `SignupForm` component from `@wasp/auth/forms/Signup` to render the signup form.
|
||||
|
||||
```tsx title="client/pages/auth/Signup.tsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
import { SignupForm } from '@wasp/auth/forms/Signup'
|
||||
|
||||
export function Signup () {
|
||||
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>
|
||||
<SignupForm />
|
||||
<br />
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
I already have an account (<Link to="/login">go to login</Link>).
|
||||
</span>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Login page
|
||||
|
||||
![Auth UI](/img/authui/login.png)
|
||||
|
||||
We are using the `LoginForm` component from `@wasp/auth/forms/Login` to render the login form.
|
||||
|
||||
```tsx title="client/pages/auth/Login.tsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
import { LoginForm } from '@wasp/auth/forms/Login'
|
||||
|
||||
export function Login() {
|
||||
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>
|
||||
<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>
|
||||
<br />
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
Forgot your password?{' '}
|
||||
<Link to="/request-password-reset">reset it</Link>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Email verification setup
|
||||
|
||||
By default, Wasp requires the e-mail to be verified before allowing the user to log in. This is done by sending a verification email to the user's email address and requiring the user to click on a link in the email to verify their email address.
|
||||
|
||||
Our setup looks like this:
|
||||
|
||||
```c title="main.wasp"
|
||||
emailVerification: {
|
||||
clientRoute: EmailVerificationRoute,
|
||||
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
|
||||
allowUnverifiedLogin: false,
|
||||
}
|
||||
```
|
||||
|
||||
When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `EmailVerificationRoute` route we defined in the `main.wasp` file.
|
||||
|
||||
```c title="main.wasp"
|
||||
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
|
||||
page EmailVerificationPage {
|
||||
component: import { EmailVerification } from "@client/pages/auth/EmailVerification.tsx",
|
||||
}
|
||||
```
|
||||
|
||||
### Email verification page
|
||||
|
||||
![Auth UI](/img/authui/email_verification.png)
|
||||
|
||||
This route goes to the `EmailVerification` page, which is defined in the `EmailVerification.tsx` file:
|
||||
|
||||
```jsx title="client/pages/auth/EmailVerification.tsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
import { VerifyEmailForm } from '@wasp/auth/forms/VerifyEmail'
|
||||
|
||||
export function EmailVerification() {
|
||||
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>
|
||||
<VerifyEmailForm />
|
||||
<br />
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
If everything is okay, <Link to="/login">go to login</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You'll notice we are using the `VerifyEmailForm` component from the `@wasp/auth/forms/VerifyEmail` module. This will give a nice-looking form for the user to verify their e-mail.
|
||||
|
||||
We will also override the default e-mail content. We are using the `getVerificationEmailContent` function from the `@server/auth/email.js` file to generate the email content.
|
||||
|
||||
```ts title="server/auth/email.ts"
|
||||
import { GetVerificationEmailContentFn } from '@wasp/types'
|
||||
|
||||
export const getVerificationEmailContent: GetVerificationEmailContentFn = ({
|
||||
verificationLink,
|
||||
}) => ({
|
||||
subject: 'Verify your email',
|
||||
text: `Click the link below to verify your email: ${verificationLink}`,
|
||||
html: `
|
||||
<p>Click the link below to verify your email</p>
|
||||
<a href="${verificationLink}">Verify email</a>
|
||||
`,
|
||||
})
|
||||
```
|
||||
|
||||
## Password reset setup
|
||||
|
||||
Users can request a password and then they'll receive an e-mail with a link to reset their password.
|
||||
|
||||
Our setup in `main.wasp` looks like this:
|
||||
|
||||
```c title="main.wasp"
|
||||
passwordReset: {
|
||||
clientRoute: PasswordResetRoute,
|
||||
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
|
||||
}
|
||||
```
|
||||
|
||||
### Request password reset page
|
||||
|
||||
![Request password reset page](/img/authui/forgot_password_after.png)
|
||||
|
||||
They request their password to be reset by going to the `/request-password-reset` page. We'll add a link to this page in the login page.
|
||||
|
||||
That page will look like this:
|
||||
|
||||
```jsx title="client/pages/auth/RequestPasswordReset.tsx"
|
||||
import { ForgotPasswordForm } from '@wasp/auth/forms/ForgotPassword'
|
||||
|
||||
export function RequestPasswordReset() {
|
||||
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>
|
||||
<ForgotPasswordForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
We will also override the default e-mail content that's sent. We are using the `getVerificationEmailContent` function from the `@server/auth/email.js` file to generate the email content.
|
||||
|
||||
```ts title="server/auth/email.ts"
|
||||
import { GetPasswordResetEmailContentFn } from '@wasp/types'
|
||||
|
||||
export const getPasswordResetEmailContent: GetPasswordResetEmailContentFn = ({
|
||||
passwordResetLink,
|
||||
}) => ({
|
||||
subject: 'Password reset',
|
||||
text: `Click the link below to reset your password: ${passwordResetLink}`,
|
||||
html: `
|
||||
<p>Click the link below to reset your password</p>
|
||||
<a href="${passwordResetLink}">Reset password</a>
|
||||
`,
|
||||
})
|
||||
```
|
||||
|
||||
### Password reset page
|
||||
|
||||
![Request password reset page](/img/authui/reset_password_after.png)
|
||||
|
||||
When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `PasswordResetRoute` route we defined in the `main.wasp` file.
|
||||
|
||||
```c title="main.wasp"
|
||||
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
|
||||
page PasswordResetPage {
|
||||
component: import { PasswordReset } from "@client/pages/auth/PasswordReset.tsx",
|
||||
}
|
||||
```
|
||||
|
||||
This route goes to the `PasswordResetPage` page, which is defined in the `PasswordReset.tsx` file. Users can enter their new password here:
|
||||
|
||||
```tsx title="client/pages/auth/PasswordReset.tsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ResetPasswordForm } from '@wasp/auth/forms/ResetPassword'
|
||||
|
||||
export function PasswordReset() {
|
||||
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>
|
||||
<ResetPasswordForm />
|
||||
<br />
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
If everything is okay, <Link to="/login">go to login</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Logout action
|
||||
|
||||
To implement the logout action, you can use the `logout` function from the `@wasp/auth/logout` module. We can add it for example, to the `Navbar` component:
|
||||
|
||||
```jsx title="client/components/Navbar.tsx"
|
||||
import logout from '@wasp/auth/logout';
|
||||
|
||||
export function Navbar() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={logout}>Logout</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Getting the user in our client & server code
|
||||
|
||||
You read about our `useAuth` hook in [this section](/docs/language/features#accessing-the-currently-logged-in-user) of the docs.
|
||||
|
||||
In short, you can use the `useAuth` hook in your client code to get the currently logged-in user. If there is no user logged in, it will return `null`.
|
||||
|
||||
```jsx title="client/pages/Profile.tsx"
|
||||
import useAuth from '@wasp/auth'
|
||||
|
||||
export function Profile() {
|
||||
const { data: user } = useAuth()
|
||||
|
||||
if (!user) {
|
||||
return <div>You are not logged in!</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
Hello, {user.email}!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
And that's it! We now have a full authentication system in our app. We can register new users, login, logout, verify their e-mail, and reset their password.
|
||||
|
||||
We hope you enjoyed this guide and that you learned something new. If you have any questions, feel free to ask them on [our Discord server](https://discord.gg/rzdnErX).
|
@ -940,7 +940,8 @@ app MyApp {
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
usernameAndPassword: {}, // use this or email, not both
|
||||
email: {}, // use this or usernameAndPassword, not both
|
||||
google: {},
|
||||
gitHub: {},
|
||||
},
|
||||
@ -954,16 +955,17 @@ app MyApp {
|
||||
`app.auth` is a dictionary with following fields:
|
||||
|
||||
#### `userEntity: entity` (required)
|
||||
Entity which represents the user (sometimes also referred to as *Principal*).
|
||||
Entity which represents the user.
|
||||
|
||||
#### `externalAuthEntity: entity` (optional)
|
||||
Entity which associates a user with some external authentication provider. We currently offer support for Google and GitHub. See the sections on [Social Login Providers](#social-login-providers-oauth-20) for more info.
|
||||
|
||||
#### `methods: dict` (required)
|
||||
List of authentication methods that Wasp app supports. Currently supported methods are:
|
||||
* `usernameAndPassword`: Provides support for authentication with a username and password. See [here](#username-and-password) for more.
|
||||
* `google`: Provides support for login via Google accounts. See [here](#social-login-providers-oauth-20) for more.
|
||||
* `gitHub`: Provides support for login via GitHub accounts. See [here](#social-login-providers-oauth-20) for more.
|
||||
* `usernameAndPassword`: authentication with a username and password. See [here](#username-and-password) for more.
|
||||
* `email`: authentication with a email and password. See [here](#email-authentication) for more.
|
||||
* `google`: authentication via Google accounts. See [here](#social-login-providers-oauth-20) for more.
|
||||
* `gitHub`: authentication via GitHub accounts. See [here](#social-login-providers-oauth-20) for more.
|
||||
|
||||
#### `onAuthFailedRedirectTo: String` (required)
|
||||
Path where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting `authRequired: true` for a specific page).
|
||||
@ -1076,7 +1078,7 @@ Validations always run on `create()`, but only when the field mentioned in `vali
|
||||
|
||||
#### Specification
|
||||
|
||||
### `login()`
|
||||
#### `login()`
|
||||
An action for logging in the user.
|
||||
```js
|
||||
login(username, password)
|
||||
@ -1098,7 +1100,7 @@ import login from '@wasp/auth/login.js'
|
||||
Login is a regular action and can be used directly from the frontend.
|
||||
|
||||
|
||||
### `signup()`
|
||||
#### `signup()`
|
||||
An action for signing up the user. This action does not log in the user, you still need to call `login()`.
|
||||
```js
|
||||
signup(userFields)
|
||||
@ -1113,7 +1115,7 @@ import signup from '@wasp/auth/signup.js'
|
||||
Signup is a regular action and can be used directly from the frontend.
|
||||
|
||||
|
||||
### `logout()`
|
||||
#### `logout()`
|
||||
An action for logging out the user.
|
||||
```js
|
||||
logout()
|
||||
@ -1135,9 +1137,6 @@ const SignOut = () => {
|
||||
}
|
||||
```
|
||||
|
||||
#### Reset password
|
||||
Coming soon.
|
||||
|
||||
#### Updating a user's password
|
||||
If you need to update user's password, you can do it safely via Prisma client, e.g. within an action:
|
||||
```js
|
||||
@ -1154,103 +1153,195 @@ You don't need to worry about hashing the password yourself - if you have an `au
|
||||
in your `.wasp` file, Wasp already set a middleware on Prisma that makes sure whenever password
|
||||
is created or updated on the user entity, it is also hashed before it is stored to the database.
|
||||
|
||||
### Email authentication
|
||||
|
||||
### Accessing the currently logged in user
|
||||
When authentication is enabled in a Wasp app, we need a way to tell whether a user is logged in and access its data.
|
||||
With that, we can further implement access control and decide which content is private and which public.
|
||||
:::info
|
||||
We have written a step-by-step guide on how to set up the e-mail authentication with Wasp's included Auth UI.
|
||||
|
||||
#### On the client
|
||||
On the client, Wasp provides a React hook you can use in functional components - `useAuth`.
|
||||
This hook is actually a thin wrapper over Wasp's [`useQuery` hook](http://localhost:3002/docs/language/features#the-usequery-hook) and returns data in the same format.
|
||||
Read more in the [email authentication guide](/docs/guides/email-auth).
|
||||
:::
|
||||
|
||||
### `useAuth()`
|
||||
#### `import statement`:
|
||||
```js
|
||||
import useAuth from '@wasp/auth/useAuth.js'
|
||||
```
|
||||
:::warning
|
||||
If a user signs up with Google or Github (and you set it up to save their social provider e-mail info on the `User` entity), they'll be able to reset their password and login with e-mail and password.
|
||||
|
||||
##### Example of usage:
|
||||
```js title="src/client/pages/MainPage.js"
|
||||
import React from 'react'
|
||||
If a user signs up with the e-mail and password and then tries to login with a social provider (Google or Github), they won't be able to do that.
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
import useAuth from '@wasp/auth/useAuth.js'
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import Todo from '../Todo.js'
|
||||
import '../Main.css'
|
||||
In the future, we will lift this limitation and enable smarter merging of accounts.
|
||||
:::
|
||||
|
||||
const Main = () => {
|
||||
const { data: user } = useAuth()
|
||||
`email` authentication method makes it possible to signup/login into the app by using an e-mail and a password.
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<span>
|
||||
Please <Link to='/login'>login</Link> or <Link to='/signup'>sign up</Link>.
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<button onClick={logout}>Logout</button>
|
||||
<Todo />
|
||||
< />
|
||||
)
|
||||
}
|
||||
```c title="main.wasp"
|
||||
app MyApp {
|
||||
title: "My app",
|
||||
// ...
|
||||
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
email: {
|
||||
// we'll deal with `email` below
|
||||
},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/someRoute"
|
||||
},
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Main
|
||||
// Wasp requires the userEntity to have at least the following fields
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
password String?
|
||||
isEmailVerified Boolean @default(false)
|
||||
emailVerificationSentAt DateTime?
|
||||
passwordResetSentAt DateTime?
|
||||
psl=}
|
||||
```
|
||||
|
||||
#### On the server
|
||||
This method requires that `userEntity` specified in `auth` contains:
|
||||
|
||||
When authentication is enabled, all operations (actions and queries) will have access to the `user` through the `context` argument. `context.user` will contain all the fields from the user entity except for the password.
|
||||
- optional `email` field of type `String`
|
||||
- optional `password` field of type `String`
|
||||
- `isEmailVerified` field of type `Boolean` with a default value of `false`
|
||||
- optional `emailVerificationSentAt` field of type `DateTime`
|
||||
- optional `passwordResetSentAt` field of type `DateTime`
|
||||
|
||||
##### Example of usage:
|
||||
```js title="src/server/actions.js"
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
#### Fields in the `email` dict
|
||||
|
||||
export const createTask = async (task, context) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(403)
|
||||
}
|
||||
```c title="main.wasp"
|
||||
app MyApp {
|
||||
title: "My app",
|
||||
// ...
|
||||
|
||||
const Task = context.entities.Task
|
||||
return Task.create({
|
||||
data: {
|
||||
description: task.description,
|
||||
user: {
|
||||
connect: { id: context.user.id }
|
||||
}
|
||||
}
|
||||
})
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
email: {
|
||||
fromField: {
|
||||
name: "My App",
|
||||
email: "hello@itsme.com"
|
||||
},
|
||||
emailVerification: {
|
||||
allowUnverifiedLogin: false,
|
||||
clientRoute: EmailVerificationRoute,
|
||||
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
|
||||
},
|
||||
passwordReset: {
|
||||
clientRoute: PasswordResetRoute
|
||||
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/someRoute"
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
In order to implement access control, each operation is responsible for checking `context.user` and
|
||||
acting accordingly - e.g. if `context.user` is `undefined` and the operation is private then user
|
||||
should be denied access to it.
|
||||
|
||||
### Validation Error Handling
|
||||
When creating, updating, or deleting entities, you may wish to handle validation errors. We have exposed a class called `AuthError` for this purpose. This could also be combined with [Prisma Error Helpers](/docs/language/features#prisma-error-helpers).
|
||||
##### `fromField: EmailFromField` (required)
|
||||
`fromField` is a dict that specifies the name and e-mail address of the sender of the e-mails sent by Wasp. It is required to be defined. The object has the following fields:
|
||||
- `name`: name of the sender (optional)
|
||||
- `email`: e-mail address of the sender
|
||||
|
||||
#### `import statement`:
|
||||
```js
|
||||
import AuthError from '@wasp/core/AuthError.js'
|
||||
##### `emailVerification: EmailVerificationConfig` (required)
|
||||
`emailVerification` is a dict that specifies the e-mail verification process. It is required to be defined.
|
||||
|
||||
The object has the following fields:
|
||||
- `clientRoute: Route`: a route that is used for the user to verify their e-mail address. (required)
|
||||
|
||||
Client route should handle the process of taking a token from the URL and sending it to the server to verify the e-mail address. You can use our `verifyEmail` action for that.
|
||||
|
||||
```js title="src/pages/EmailVerificationPage.jsx"
|
||||
import { verifyEmail } from '@wasp/auth/email/actions';
|
||||
...
|
||||
await verifyEmail({ token });
|
||||
```
|
||||
|
||||
##### Example of usage:
|
||||
```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
|
||||
}
|
||||
}
|
||||
Read on how to do it the easiest way with Auth UI in the [email authentication guide](/docs/guides/email-auth).
|
||||
|
||||
- `getEmailContentFn: ServerImport`: a function that returns the content of the e-mail that is sent to the user. (optional)
|
||||
|
||||
Defining `getEmailContentFn` can be done by defining a Javscript or Typescript file in the `server` directory.
|
||||
|
||||
```ts title="server/email.ts"
|
||||
import { GetVerificationEmailContentFn } from '@wasp/types'
|
||||
|
||||
export const getVerificationEmailContent: GetVerificationEmailContentFn = ({
|
||||
verificationLink,
|
||||
}) => ({
|
||||
subject: 'Verify your email',
|
||||
text: `Click the link below to verify your email: ${verificationLink}`,
|
||||
html: `
|
||||
<p>Click the link below to verify your email</p>
|
||||
<a href="${verificationLink}">Verify email</a>
|
||||
`,
|
||||
})
|
||||
```
|
||||
|
||||
## Social Login Providers (OAuth 2.0)
|
||||
- `allowUnverifiedLogin: Boolean`: a boolean that specifies whether the user can login without verifying their e-mail address. (optional)
|
||||
|
||||
It defaults to `false`. If `allowUnverifiedLogin` is set to `true`, the user can login without verifying their e-mail address, otherwise users will receive a `401` error when trying to login without verifying their e-mail address.
|
||||
|
||||
##### `passwordReset: PasswordResetConfig` (required)
|
||||
`passwordReset` is a dict that specifies the password reset process. It is required to be defined. The object has the following fields:
|
||||
- `clientRoute: Route`: a route that is used for the user to reset their password. (required)
|
||||
|
||||
Client route should handle the process of taking a token from the URL and a new password from the user and sending it to the server. You can use our `requestPasswordReset` and `resetPassword` actions to do that.
|
||||
|
||||
```js title="src/pages/ForgotPasswordPage.jsx"
|
||||
import { requestPasswordReset } from '@wasp/auth/email/actions';
|
||||
...
|
||||
await requestPasswordReset({ email });
|
||||
```
|
||||
|
||||
```js title="src/pages/PasswordResetPage.jsx"
|
||||
import { resetPassword } from '@wasp/auth/email/actions';
|
||||
...
|
||||
await resetPassword({ password, token })
|
||||
```
|
||||
|
||||
|
||||
Read on how to do it the easiest way with Auth UI in the [email authentication guide](/docs/guides/email-auth).
|
||||
|
||||
- `getEmailContentFn: ServerImport`: a function that returns the content of the e-mail that is sent to the user. (optional)
|
||||
|
||||
Defining `getEmailContentFn` is done by defining a function that looks like this:
|
||||
|
||||
```ts title="server/email.ts"
|
||||
import { GetPasswordResetEmailContentFn } from '@wasp/types'
|
||||
|
||||
export const getPasswordResetEmailContent: GetPasswordResetEmailContentFn = ({
|
||||
passwordResetLink,
|
||||
}) => ({
|
||||
subject: 'Password reset',
|
||||
text: `Click the link below to reset your password: ${passwordResetLink}`,
|
||||
html: `
|
||||
<p>Click the link below to reset your password</p>
|
||||
<a href="${passwordResetLink}">Reset password</a>
|
||||
`,
|
||||
})
|
||||
```
|
||||
|
||||
#### Email sender for email authentication
|
||||
|
||||
We require that you define an `emailSender`, so that Wasp knows how to send e-mails. Read more about that [here](#email-sender).
|
||||
|
||||
#### Validations
|
||||
|
||||
We provide basic validations out of the box. The validations are:
|
||||
- `email`: non-empty, valid e-mail address
|
||||
- `password`: non-empty, at least 8 characters, and contains a number
|
||||
|
||||
Note that `email`s are stored in a case-insensitive manner.
|
||||
|
||||
:::info
|
||||
You don't need to worry about hashing the password yourself! Even when you are using Prisma's client directly and calling `create()` with a plain-text password, Wasp's middleware takes care of hashing it before storing it in the database. An additional middleware also performs field validation.
|
||||
:::
|
||||
|
||||
|
||||
|
||||
### Social Login Providers (OAuth 2.0)
|
||||
Wasp allows you to easily add social login providers to your app.
|
||||
|
||||
The following is a list of links to guides that will help you get started with the currently supported providers:
|
||||
@ -1262,7 +1353,7 @@ When using Social Login Providers, Wasp gives you the following options:
|
||||
- UI Helpers to make it easy to add social login buttons and actions
|
||||
- Override settings to customize the behavior of the providers
|
||||
|
||||
### Default Settings
|
||||
#### Default Settings
|
||||
|
||||
|
||||
<Tabs>
|
||||
@ -1345,7 +1436,7 @@ psl=}
|
||||
:::note
|
||||
the same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity).
|
||||
:::
|
||||
### UI helpers
|
||||
#### UI helpers
|
||||
|
||||
Wasp provides sign-in buttons, logos and URLs for your login page:
|
||||
|
||||
@ -1373,7 +1464,7 @@ export default Login
|
||||
|
||||
If you need more customization than what the buttons provide, you can create your own custom components using the `signInUrl`s.
|
||||
|
||||
### Overrides
|
||||
#### Overrides
|
||||
|
||||
When a user signs in for the first time, Wasp will create a new User account and link it to the chosen Auth Provider account for future logins. If the `userEntity` contains a `username` field it will default to a random dictionary phrase that does not exist in the database, such as `nice-blue-horse-27160`. This is a historical coupling between auth methods that will be removed over time.
|
||||
|
||||
@ -1491,6 +1582,103 @@ This function should return the user fields to use when creating a new user upon
|
||||
- `generateAvailableDictionaryUsername` generates a random dictionary phrase that is not yet in the database. For example, `nice-blue-horse-27160`.
|
||||
|
||||
|
||||
### Validation Error Handling
|
||||
When creating, updating, or deleting entities, you may wish to handle validation errors. We have exposed a class called `AuthError` for this purpose. This could also be combined with [Prisma Error Helpers](/docs/language/features#prisma-error-helpers).
|
||||
|
||||
#### `import statement`:
|
||||
```js
|
||||
import AuthError from '@wasp/core/AuthError.js'
|
||||
```
|
||||
|
||||
##### Example of usage:
|
||||
```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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Accessing the currently logged in user
|
||||
When authentication is enabled in a Wasp app, we need a way to tell whether a user is logged in and access its data.
|
||||
With that, we can further implement access control and decide which content is private and which public.
|
||||
|
||||
#### On the client
|
||||
On the client, Wasp provides a React hook you can use in functional components - `useAuth`.
|
||||
This hook is actually a thin wrapper over Wasp's [`useQuery` hook](http://localhost:3002/docs/language/features#the-usequery-hook) and returns data in the same format.
|
||||
|
||||
### `useAuth()`
|
||||
#### `import statement`:
|
||||
```js
|
||||
import useAuth from '@wasp/auth/useAuth'
|
||||
```
|
||||
|
||||
##### Example of usage:
|
||||
```js title="src/client/pages/MainPage.js"
|
||||
import React from 'react'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
import useAuth from '@wasp/auth/useAuth'
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import Todo from '../Todo.js'
|
||||
import '../Main.css'
|
||||
|
||||
const Main = () => {
|
||||
const { data: user } = useAuth()
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<span>
|
||||
Please <Link to='/login'>login</Link> or <Link to='/signup'>sign up</Link>.
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<button onClick={logout}>Logout</button>
|
||||
<Todo />
|
||||
< />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Main
|
||||
```
|
||||
|
||||
#### On the server
|
||||
|
||||
### `context.user`
|
||||
|
||||
When authentication is enabled, all operations (actions and queries) will have access to the `user` through the `context` argument. `context.user` will contain all the fields from the user entity except for the password.
|
||||
|
||||
##### Example of usage:
|
||||
```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 }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
In order to implement access control, each operation is responsible for checking `context.user` and
|
||||
acting accordingly - e.g. if `context.user` is `undefined` and the operation is private then user
|
||||
should be denied access to it.
|
||||
|
||||
## Client configuration
|
||||
|
||||
You can configure the client using the `client` field inside the `app`
|
||||
|
@ -53,13 +53,14 @@ module.exports = {
|
||||
items: [
|
||||
'integrations/github',
|
||||
'integrations/google',
|
||||
'guides/email-auth',
|
||||
]
|
||||
},
|
||||
'integrations/css-frameworks',
|
||||
'deploying',
|
||||
'typescript',
|
||||
'guides/testing',
|
||||
'guides/sending-emails'
|
||||
'guides/sending-emails',
|
||||
],
|
||||
|
||||
},
|
||||
|
BIN
web/static/img/authui/all_screens.gif
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
web/static/img/authui/email_verification.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
web/static/img/authui/forgot_password.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
web/static/img/authui/forgot_password_after.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
web/static/img/authui/login.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
web/static/img/authui/reset_password.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
web/static/img/authui/reset_password_after.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
web/static/img/authui/signup.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
web/static/img/authui/signup_after.png
Normal file
After Width: | Height: | Size: 36 KiB |