mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-29 04:42:29 +03:00
640 lines
17 KiB
Markdown
640 lines
17 KiB
Markdown
|
---
|
||
|
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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//...
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//...
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
<small>
|
||
|
|
||
|
Read more about the `auth` field options in the [API Reference](#api-reference) section.
|
||
|
|
||
|
</small>
|
||
|
|
||
|
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:
|
||
|
|
||
|
<AuthMethodsGrid />
|
||
|
|
||
|
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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```wasp title="main.wasp"
|
||
|
page MainPage {
|
||
|
component: import Main from "@client/pages/Main.jsx",
|
||
|
authRequired: true
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```wasp title="main.wasp"
|
||
|
page MainPage {
|
||
|
component: import Main from "@client/pages/Main.tsx",
|
||
|
authRequired: true
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
:::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:
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```jsx title="client/components/LogoutButton.jsx"
|
||
|
import logout from '@wasp/auth/logout'
|
||
|
|
||
|
const LogoutButton = () => {
|
||
|
return (
|
||
|
<button onClick={logout}>Logout</button>
|
||
|
)
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```tsx title="client/components/LogoutButton.tsx"
|
||
|
import logout from '@wasp/auth/logout'
|
||
|
|
||
|
const LogoutButton = () => {
|
||
|
return (
|
||
|
<button onClick={logout}>Logout</button>
|
||
|
)
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
## 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:
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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 (
|
||
|
<div>
|
||
|
<Button onClick={logout}>Logout</Button>
|
||
|
{JSON.stringify(user, null, 2)}
|
||
|
</div>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export default AccountPage;
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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 (
|
||
|
<div>
|
||
|
<Button onClick={logout}>Logout</Button>
|
||
|
{JSON.stringify(user, null, 2)}
|
||
|
</div>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export default AccountPage;
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
#### 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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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 (
|
||
|
<span>
|
||
|
Please <Link to='/login'>login</Link> or <Link to='/signup'>sign up</Link>.
|
||
|
</span>
|
||
|
)
|
||
|
} else {
|
||
|
return (
|
||
|
<>
|
||
|
<button onClick={logout}>Logout</button>
|
||
|
<Todo />
|
||
|
< />
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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 (
|
||
|
<span>
|
||
|
Please <Link to='/login'>login</Link> or <Link to='/signup'>sign up</Link>.
|
||
|
</span>
|
||
|
)
|
||
|
} else {
|
||
|
return (
|
||
|
<>
|
||
|
<button onClick={logout}>Logout</button>
|
||
|
<Todo />
|
||
|
< />
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
:::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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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 }
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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<Task, "description">
|
||
|
|
||
|
export const createTask: CreateTask<CreateTaskPayload, Task> = 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 }
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
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:
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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!'
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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<UpdatePasswordPayload, User> = async (args, context) => {
|
||
|
return context.entities.User.update({
|
||
|
where: { id: args.userId },
|
||
|
data: {
|
||
|
password: 'New pwd which will be hashed automatically!'
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
### 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:
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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)
|
||
|
},
|
||
|
]
|
||
|
})
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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)
|
||
|
},
|
||
|
]
|
||
|
})
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
:::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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
## API Reference
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//...
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//...
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
`app.auth` is a dictionary with the following fields:
|
||
|
|
||
|
#### `userEntity: entity` <Required />
|
||
|
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.
|
||
|
|
||
|
<Tabs groupId="js-ts">
|
||
|
<TabItem value="js" label="JavaScript">
|
||
|
|
||
|
```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=}
|
||
|
```
|
||
|
</TabItem>
|
||
|
<TabItem value="ts" label="TypeScript">
|
||
|
|
||
|
```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=}
|
||
|
```
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
:::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` <Required />
|
||
|
A dictionary of auth methods enabled for the app.
|
||
|
|
||
|
<AuthMethodsGrid />
|
||
|
|
||
|
#### `onAuthFailedRedirectTo: String` <Required />
|
||
|
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).
|
||
|
:::
|