Skip to content

Commit

Permalink
chore(private-set): Wrap profile page in <PrivateSet> instead of Priv…
Browse files Browse the repository at this point in the history
…ate (#9575)

Co-authored-by: Daniel Choudhury <dannychoudhury@gmail.com>
  • Loading branch information
2 people authored and jtoar committed Dec 1, 2023
1 parent 43b0ae5 commit e7b2e44
Show file tree
Hide file tree
Showing 16 changed files with 101 additions and 104 deletions.
13 changes: 6 additions & 7 deletions docs/docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,11 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />

// highlight-start
<Private unauthenticated="login">
{/* Or... <Set private unauthenticated="login"> */}
<PrivateSet unauthenticated="login">
// highlight-end
<Route path="/admin" page={AdminPage} name="admin" />
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
<PrivateSet>
</Router>
)
}
Expand All @@ -153,19 +152,19 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />
<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />

<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
<PrivateSet>

// highlight-next-line
<Set private unauthenticated="forbidden" hasRole="admin">
<Route path="/admin" page={AdminPage} name="admin" />
</Set>

// highlight-next-line
<Private unauthenticated="forbidden" hasRole={['author', 'editor']}>
<PrivateSet unauthenticated="forbidden" hasRole={['author', 'editor']}>
<Route path="/posts" page={PostsPage} name="posts" />
</Private>
<PrivateSet>
</Router>
)
}
Expand Down
24 changes: 12 additions & 12 deletions docs/docs/how-to/role-based-access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,52 +238,52 @@ export const getCurrentUser = async (decoded) => {
#### How to Protect a Route
To protect a `Private` route for access by a single role:
To protect a `PrivateSet` route for access by a single role:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="home" roles="admin">
<PrivateSet unauthenticated="home" roles="admin">
<Route path="/admin/users" page={UsersPage} name="users" />
</Private>
</PrivateSet>
</Router>
)
}
```
To protect a `Private` route for access by a multiple roles:
To protect a `PrivateSet` route for access by a multiple roles:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="home" roles={['admin', 'editor', 'publisher']}>
<PrivateSet unauthenticated="home" roles={['admin', 'editor', 'publisher']}>
<Route path="/admin/posts/{id:Int}/edit" page={EditPostPage} name="editPost" />
</Private>
</PrivateSet>
</Router>
)
}
```
> Note: If you are using `Set` you can use its `private` attribute instead of the `<Private>` component.
> Note: If you are using `Set` you can use its `private` attribute instead of the `<PrivateSet>` component.
If the currentUser is not assigned the role, they will be redirected to the page specified in the `unauthenticated` property. Therefore, you can define a specific page to be seen when attempting to access the protected route and denied access such as a "forbidden" page:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="forbidden" roles="admin">
<PrivateSet unauthenticated="forbidden" roles="admin">
<Route path="/settings" page={SettingsPage} name="settings" />
<Route path="/admin" page={AdminPage} name="sites" />
</Private>
</PrivateSet>

<Route notfound page={NotFoundPage} />
<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/prerender.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ This will prerender your NotFoundPage to `404.html` in your dist folder. Note th
For Private Routes, Redwood prerenders your Private Routes' `whileLoadingAuth` prop:

```jsx
<Private >
<PrivateSet>
// Loading is shown while we're checking to see if the user's logged in
<Route path="/super-secret-admin-dashboard" page={SuperSecretAdminDashboard} name="ssad" whileLoadingAuth={() => <Loading />} prerender/>
</Private>
</PrivateSet>
```

### Rendering skeletons while authenticating
Sometimes you want to render the shell of the page, while you wait for your authentication checks to happen. This can make the experience feel a lot snappier to the user, since they don't wait on a blank screen while their credentials are checked.

To do this, make use of the `whileLoadingAuth` prop on `<Private>` or a `<Set private>` in your Routes file. For example, if we have a dashboard that you need to be logged in to access:
To do this, make use of the `whileLoadingAuth` prop on `<PrivateSet>` in your Routes file. For example, if we have a dashboard that you need to be logged in to access:

```js ./web/src/Routes.{tsx,js}
// This renders the layout with skeleton loaders in the content area
Expand Down
24 changes: 11 additions & 13 deletions docs/docs/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ The `path` prop specifies the URL path to match, starting with the beginning sla

Some pages should only be visible to authenticated users.

We support this using private `<Set>`s or the `<Private>` component. Read more [further down](#private-set).

## Sets of Routes

You can group Routes into sets using the `Set` component. `Set` allows you to wrap a set of Routes in another component or array of components—usually a Context, a Layout, or both:
Expand Down Expand Up @@ -145,16 +143,16 @@ Here's an example of how you'd use a private set:
</Router>
```

Private routes are important and should be easy to spot in your Routes file. The larger your Routes file gets, the more difficult it will probably become to find `<Set private /*...*/>` among your other Sets. So we also provide a `<Private>` component that's just an alias for `<Set private /*...*/>`. Most of our documentation uses `<Private>`.
Private routes are important and should be easy to spot in your Routes file. The larger your Routes file gets, the more difficult it will probably become to find `<Set private /*...*/>` among your other Sets. So we also provide a `<PrivateSet>` component that's just an alias for `<Set private /*...*/>`. Most of our documentation uses `<PrivateSet>`.

Here's the same example again, but now using `<Private>`
Here's the same example again, but now using `<PrivateSet>`

```jsx title="Routes.js"
<Router>
<Route path="/" page={HomePage} name="home" />
<Private unauthenticated="home">
<PrivateSet unauthenticated="home">
<Route path="/admin" page={AdminPage} name="admin" />
</Private>
<PrivateSet>
</Router>
```

Expand All @@ -164,9 +162,9 @@ To protect `Private` routes for access by a single role:

```jsx title="Routes.js"
<Router>
<Private unauthenticated="forbidden" roles="admin">
<PrivateSet unauthenticated="forbidden" roles="admin">
<Route path="/admin/users" page={UsersPage} name="users" />
</Private>
<PrivateSet>

<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
</Router>
Expand All @@ -176,9 +174,9 @@ To protect `Private` routes for access by multiple roles:

```jsx title="Routes.js"
<Router>
<Private unauthenticated="forbidden" roles={['admin', 'editor', 'publisher']}>
<PrivateSet unauthenticated="forbidden" roles={['admin', 'editor', 'publisher']}>
<Route path="/admin/posts/{id:Int}/edit" page={EditPostPage} name="editPost" />
</Private>
<PrivateSet>

<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
</Router>
Expand Down Expand Up @@ -574,13 +572,13 @@ When the lazy-loaded page is loading, `PageLoadingContext.Consumer` will pass `{

Let's say you have a dashboard area on your Redwood app, which can only be accessed after logging in. When Redwood Router renders your private page, it will first fetch the user's details, and only render the page if it determines the user is indeed logged in.

In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private `<Route>`, `<Set private>` or the `<Private>` component:
In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private `<Route>`, `<Set private>` or the `<PrivateSet>` component:

```jsx
//Routes.js

<Router>
<Private
<PrivateSet
wrap={DashboardLayout}
unauthenticated="login"
whileLoadingAuth={SkeletonLoader} //<-- auth loader
Expand All @@ -590,7 +588,7 @@ In order to display a loader while auth details are being retrieved you can add
<Route path="/dashboard" page={DashboardHomePage} name="dashboard" />

{/* other routes */}
</Private>
<PrivateSet>
</Router>
```

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/tutorial/chapter0/what-is-redwood.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const Routes = () => {
<Set wrap={ApplicationLayout}>
<Route path="/login" page={LoginPage} name="login" />
<Route path="/signup" page={SignupPage} name="signup" />
<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
<Route path="/dashboard" page={DashboardPage} name="dashboard" />
<Route path="/products/{sku}" page={ProductsPage} name="products" />
</Private>
</PrivateSet>
</Set>

<Route path="/" page={HomePage} name="home" />
Expand All @@ -54,7 +54,7 @@ const Routes = () => {
}
```

You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `<Private>` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.
You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `<PrivateSet>` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.

#### Prerender

Expand All @@ -66,7 +66,7 @@ This is Redwood's version of static site generation, aka SSG.

### Authentication

The `<Private>` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!
The `<PrivateSet>` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!

Once authenticated, how do you know what a user is allowed to do or not do? Redwood includes helpers for [role-based access control](https://redwoodjs.com/docs/how-to/role-based-access-control-rbac) that integrates on both the front- and backend.

Expand Down
16 changes: 8 additions & 8 deletions docs/docs/tutorial/chapter4/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Try reloading the Posts admin and we'll see something that's 50% correct:
![image](https://user-images.githubusercontent.com/300/146462761-d21c93f0-289a-4e11-bccf-8e4e68f21438.png)
Going to the admin section now prevents a non-logged in user from seeing posts, great! This is the result of the `@requireAuth` directive in `api/src/graphql/posts.sdl.{js,ts}`: you're not authenticated so GraphQL will not respond to your request for data. But, ideally they wouldn't be able to see the admin pages themselves. Let's fix that with a new component in the Routes file, `<Private>`:
Going to the admin section now prevents a non-logged in user from seeing posts, great! This is the result of the `@requireAuth` directive in `api/src/graphql/posts.sdl.{js,ts}`: you're not authenticated so GraphQL will not respond to your request for data. But, ideally they wouldn't be able to see the admin pages themselves. Let's fix that with a new component in the Routes file, `</PrivateSet>`:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
Expand All @@ -213,15 +213,15 @@ const Routes = () => {
return (
<Router useAuth={useAuth}>
// highlight-next-line
<Private unauthenticated="home">
</PrivateSet unauthenticated="home">
<Set wrap={ScaffoldLayout} title="Posts" titleTo="posts" buttonLabel="New Post" buttonTo="newPost">
<Route path="/admin/posts/new" page={PostNewPostPage} name="newPost" />
<Route path="/admin/posts/{id:Int}/edit" page={PostEditPostPage} name="editPost" />
<Route path="/admin/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/admin/posts" page={PostPostsPage} name="posts" />
</Set>
// highlight-next-line
</Private>
</PrivateSet>
<Set wrap={BlogLayout}>
<Route path="/article/{id:Int}" page={ArticlePage} name="article" />
<Route path="/contact" page={ContactPage} name="contact" />
Expand Down Expand Up @@ -252,15 +252,15 @@ const Routes = () => {
return (
<Router useAuth={useAuth}>
// highlight-next-line
<Private unauthenticated="home">
</PrivateSet unauthenticated="home">
<Set wrap={ScaffoldLayout} title="Posts" titleTo="posts" buttonLabel="New Post" buttonTo="newPost">
<Route path="/admin/posts/new" page={PostNewPostPage} name="newPost" />
<Route path="/admin/posts/{id:Int}/edit" page={PostEditPostPage} name="editPost" />
<Route path="/admin/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/admin/posts" page={PostPostsPage} name="posts" />
</Set>
// highlight-next-line
</Private>
</PrivateSet>
<Set wrap={BlogLayout}>
<Route path="/article/{id:Int}" page={ArticlePage} name="article" />
<Route path="/contact" page={ContactPage} name="contact" />
Expand All @@ -278,7 +278,7 @@ export default Routes
</TabItem>
</Tabs>
We wrap the routes we want to be private (that is, only accessible when logged in) in the `<Private>` component, and tell our app where to send them if they are unauthenticated. In this case they should go to the `home` route.
We wrap the routes we want to be private (that is, only accessible when logged in) in the `</PrivateSet>` component, and tell our app where to send them if they are unauthenticated. In this case they should go to the `home` route.
Try going back to [http://localhost:8910/admin/posts](http://localhost:8910/admin/posts) now and—yikes!
Expand All @@ -288,7 +288,7 @@ Well, we couldn't get to the admin pages, but we also can't see our blog posts a
It's because the `posts` query in `posts.sdl.{js,ts}` is used by both the homepage *and* the posts admin page. Since it has the `@requireAuth` directive, it's locked down and can only be accessed when logged in. But we *do* want people that aren't logged in to be able to view the posts on the homepage!
Now that our admin pages are behind a `<Private>` route, what if we set the `posts` query to be `@skipAuth` instead? Let's try:
Now that our admin pages are behind a `</PrivateSet>` route, what if we set the `posts` query to be `@skipAuth` instead? Let's try:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
Expand Down Expand Up @@ -888,7 +888,7 @@ You can generate a new value with the `yarn rw g secret` command. It only output
## Wrapping Up
Believe it or not, that's pretty much it for authentication! You can use the combination of `@requireAuth` and `@skipAuth` directives to lock down access to GraphQL query/mutations, and the `<Private>` component to restrict access to entire pages of your app. If you only want to restrict access to certain components, or certain parts of a component, you can always get `isAuthenticated` from the `useAuth()` hook and then render one thing or another.
Believe it or not, that's pretty much it for authentication! You can use the combination of `@requireAuth` and `@skipAuth` directives to lock down access to GraphQL query/mutations, and the `</PrivateSet>` component to restrict access to entire pages of your app. If you only want to restrict access to certain components, or certain parts of a component, you can always get `isAuthenticated` from the `useAuth()` hook and then render one thing or another.
Head over to the Redwood docs to read more about [self-hosted](../../auth/dbauth.md) and [third-party authentication](../../authentication.md#official-integrations).
Expand Down
20 changes: 10 additions & 10 deletions docs/versioned_docs/version-6.0/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ Much of what the functions it returns do is self explanatory, but the options th

### Protecting routes

You can require that a user be authenticated to navigate to a route by wrapping it in the `Private` component or the `Set` component with the `private` prop set to `true`.
You can require that a user be authenticated to navigate to a route by wrapping it in the `PrivateSet` component.
An unauthenticated user will be redirected to the route specified in either component's `unauthenticated` prop:

```tsx title="web/src/Routes.tsx"
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
Expand All @@ -130,21 +130,21 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />

// highlight-start
<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
{/* Or... <Set private unauthenticated="login"> */}
// highlight-end
<Route path="/admin" page={AdminPage} name="admin" />
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
</PrivateSet>
</Router>
)
}
```

You can also restrict access by role by passing a role or an array of roles to the `Private` or `Set` component's `hasRole` prop:
You can also restrict access by role by passing a role or an array of roles to the `PrivateSet` component's `hasRole` prop:

```tsx title="web/src/Routes.tsx"
import { Router, Route, Private, Set } from '@redwoodjs/router'
import { Router, Route, PrivateSet, Set } from '@redwoodjs/router'

const Routes = () => {
return (
Expand All @@ -153,19 +153,19 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />
<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />

<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
</PrivateSet>

// highlight-next-line
<Set private unauthenticated="forbidden" hasRole="admin">
<Route path="/admin" page={AdminPage} name="admin" />
</Set>

// highlight-next-line
<Private unauthenticated="forbidden" hasRole={['author', 'editor']}>
<PrivateSet unauthenticated="forbidden" hasRole={['author', 'editor']}>
<Route path="/posts" page={PostsPage} name="posts" />
</Private>
</PrivateSet>
</Router>
)
}
Expand Down
Loading

0 comments on commit e7b2e44

Please sign in to comment.