New docs structures, added docs for OAuth providers and team selections (#116)

* new doc structure

* added domain docs

* added team selection

* added oauth page, improved navigation

* improved team-selection

* improved code styling, updated permissions docs

* improved wording in teams and overview

* added team switcher updates

* updated production docs

* added oauth provider docs

* updated docs navigation
This commit is contained in:
Zai Shi 2024-06-30 17:44:32 -07:00 committed by GitHub
parent b9ebd30d10
commit 436cd95bf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 692 additions and 334 deletions

View File

@ -6,15 +6,15 @@ title: Stack Auth Documentation
tabs:
documentation:
display-name: Documentation
icon: 'fa-solid fa-home'
icon: fa-solid fa-home
slug: docs
sdk:
display-name: SDK Reference
icon: 'fa-solid fa-hammer'
icon: fa-solid fa-hammer
slug: sdk
api:
display-name: API Reference
icon: 'fa-solid fa-code'
icon: fa-solid fa-code
slug: rest-api
navigation:
@ -23,22 +23,53 @@ navigation:
- section: Get Started
contents:
- page: Overview
icon: fa-regular fa-globe
path: ./docs/pages/getting-started/overview.mdx
- page: Installation & Setup
icon: fa-regular fa-download
path: ./docs/pages/getting-started/setup.mdx
- page: Users & Protected Pages
- page: Users
icon: fa-regular fa-address-book
path: ./docs/pages/getting-started/users.mdx
- page: Teams & Permissions
path: ./docs/pages/getting-started/teams.mdx
# - page: Protecting Pages
# icon: fa-regular fa-shield-check
# path: ./docs/pages/getting-started/protecting-pages.mdx
- page: Going to Production
icon: fa-regular fa-rocket
path: ./docs/pages/getting-started/production.mdx
- section: Concepts
contents:
# - page: Client vs. Server
# icon: fa-regular fa-code-branch
- page: OAuth Providers
icon: fa-regular fa-key
path: ./docs/pages/concepts/oauth.mdx
- page: Teams
icon: fa-regular fa-users
path: ./docs/pages/concepts/teams.mdx
- page: Selecting a Team
icon: fa-regular fa-exchange
path: ./docs/pages/concepts/team-selection.mdx
- page: Permissions
icon: fa-regular fa-user-lock
path: ./docs/pages/concepts/permissions.mdx
# - page: Local Development
# icon: fa-regular fa-laptop
- section: Customization
contents:
- page: Dark/Light Mode
icon: fa-regular fa-circle-half-stroke
path: ./docs/pages/customization/dark-mode.mdx
- page: Colors and Styles
icon: fa-regular fa-paint-brush
path: ./docs/pages/customization/custom-styles.mdx
# - page: Customize Emails
# icon: fa-regular fa-envelope
- page: Custom Layouts and Pages
icon: fa-regular fa-table-layout
path: ./docs/pages/customization/custom-pages.mdx
- section: Custom Page Examples
icon: fa-regular fa-files
contents:
- page: Sign In
path: ./docs/pages/customization/page-examples/sign-in.mdx
@ -84,11 +115,11 @@ colors:
light: '#FFFFFF'
dark: '#000000'
sidebar-background:
light: '#FFFFFF'
dark: '#000000'
light: '#FCFCFC'
dark: '#090909'
card-background:
light: '#FFFFFF'
dark: '#111111'
light: '#FCFCFC'
dark: '#090909'
layout:
page-width: full
content-width: 40rem

View File

@ -0,0 +1,91 @@
---
slug: concepts/oauth
subtitle: Integrating OAuth Providers for API Access
---
With Stack, users can connect multiple OAuth accounts, enabling access to provider-specific data and services such as Google Drive, Microsoft Calendar, GitHub repositories, and more.
## Connecting with an OAuth Provider Post Sign-in
Users can connect with an OAuth provider during sign-in or after signing in using other methods. For the latter, use the `user.useConnectedAccount('<provider_name>', { or: 'redirect' })` method. If the account is already connected, the method returns a Connection object; otherwise, it redirects the user to the provider's authorization page. Heres how to connect with Google:
```jsx
'use client';
import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
// Redirects to Google authorization if not already connected
const account = user.useConnectedAccount('google', { or: 'redirect' });
// Account is always defined because of the redirect
return <div>Google account connected</div>;
}
```
## Connecting with Extra Scopes
You can request extra scopes when connecting with an OAuth provider. For instance, to access Google Drive, pass the `https://www.googleapis.com/auth/drive` scope. Heres an example:
```jsx
'use client';
import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
// Redirects to the Google authorization page, requesting access to Google Drive
const account = user.useConnectedAccount('google', { or: 'redirect' });
// Account is always defined because of the redirect
return <div>Google Drive connected</div>;
}
```
## Retrieving the Access Token
Once connected with an OAuth provider, obtain the access token using the `account.getAccessToken()` method. Use this token to access the providers API endpoints. Heres an example of using the access token to interact with Google Drive API:
```jsx
'use client';
import { useEffect, useState } from 'react';
import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
const account = user.useConnectedAccount('google', { or: 'redirect' });
const tokens = account.getAccessToken();
const [response, setResponse] = useState(null);
useEffect(() => {
fetch('https://www.googleapis.com/drive/v3/files', {
headers: {
Authorization: `Bearer ${tokens.access_token}`
}
})
.then(res => res.json())
.then(data => setResponse(data));
}, [tokens]);
return <div>{response ? JSON.stringify(response) : 'Loading...'}</div>;
}
```
## Requesting Extra Scopes During Sign-in
To avoid showing the authorization page twice, you can request extra scopes during the sign-in process. This approach is optional and depends on your application's design. Some applications may prefer to request extra permissions only when needed, while others might want to obtain all necessary permissions upfront.
Configure the `StackServerApp` with the required scopes:
```jsx title='stack.ts'
// imports ...
export const stackServerApp = new StackServerApp({
// your other settings ...
oauthScopesOnSignIn: {
google: ['https://www.googleapis.com/auth/drive']
}
});
```
By setting this up, users will be prompted for all necessary permissions during the initial sign-in, avoiding the need for them to approve additional permissions later. Choose the approach that best fits your application's user experience and workflow.

View File

@ -0,0 +1,130 @@
---
slug: concepts/permissions
subtitle: Control what each user can do and access with the permission system
---
## Team Permissions
Team permissions control what a user can do within each team. You can create and assign permissions to team members from the Stack dashboard. These permissions could include actions like `create_post` or `read_secret_info`, or roles like `admin` or `moderator`. Within your app, you can verify if a user has a specific permission within a team.
Permissions can be nested to create a hierarchical structure. For example, an `admin` permission can include both `moderator` and `user` permissions. We provide tools to help you verify whether a user has a permission directly or indirectly.
### Creating a Permission
To create a new permission, navigate to the `Team Permissions` section of the Stack dashboard. You can select the permissions that the new permission will contain. Any permissions included within these selected permissions will also be recursively included.
### System Permissions
Stack comes with a few predefined team permissions known as system permissions. These permissions start with a dollar sign (`$`). While you can assign these permissions to members or include them within other permissions, you cannot modify them as they are integral to the Stack backend system.
### Checking if a User has a Permission
To check whether a user has a specific permission, use the `getPermission` method or the `usePermission` hook on the `User` object. This returns the `Permission` object if the user has it; otherwise, it returns `null`. Always perform permission checks on the server side for business logic, as client-side checks can be bypassed. Heres an example:
<Tabs>
<Tab title="Client Component">
```tsx title="Check user permission on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function CheckUserPermission() {
const user = useUser({ or: 'redirect' });
const permission = user.usePermission('read');
// Don't rely on client-side permission checks for business logic.
return (
<div>
{permission ? 'You have the read permission' : 'You shall not pass'}
</div>
);
}
```
</Tab>
<Tab title="Server Component">
```tsx title="Check user permission on the server"
import { stackServerApp } from "@/stack";
export default async function CheckUserPermission() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const permission = await user.getPermission('read');
// This is a server-side check, so it's secure.
return (
<div>
{permission ? 'You have the read permission' : 'You shall not pass'}
</div>
);
}
```
</Tab>
</Tabs>
### Listing All Permissions of a User
To get a list of all permissions a user has, use the `listPermissions` method or the `usePermissions` hook on the `User` object. This method retrieves both direct and indirect permissions. Here is an example:
<Tabs>
<Tab title="Client Component" default>
```tsx title="List user permissions on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserPermissions() {
const user = useUser({ or: 'redirect' });
const permissions = user.usePermissions();
return (
<div>
{permissions.map(permission => (
<div key={permission.id}>{permission.id}</div>
))}
</div>
);
}
```
</Tab>
<Tab title="Server Component">
```tsx title="List user permissions on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserPermissions() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const permissions = await user.listPermissions();
return (
<div>
{permissions.map(permission => (
<div key={permission.id}>{permission.id}</div>
))}
</div>
);
}
```
</Tab>
</Tabs>
### Granting a Permission to a User
To grant a permission to a user, use the `grantPermission` method on the `ServerUser`. Heres an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const user = await stackServerApp.getUser();
await user.grantPermission(team, 'read');
```
### Revoking a Permission from a User
To revoke a permission from a user, use the `revokePermission` method on the `ServerUser`. Heres an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const user = await stackServerApp.getUser();
await user.revokePermission(team, 'read');
```
By following these guidelines, you can efficiently manage and verify team permissions within your application.

View File

@ -0,0 +1,119 @@
---
slug: concepts/team-selection
subtitle: Switch between multiple teams of a user
---
A user can be a member of multiple teams, so most websites using teams will need a way to select a "current team" that the user is working on. There are two primary methods to accomplish this:
- **Deep Link**: Each team has a unique URL, for example, `your-website.com/team/<team-id>`. When a team is selected, it redirects to a page with that team's URL.
- **Current Team**: When a user selects a team, the app stores the team as a global "current team" state. In this way, the URL of the current team might be something like `your-website.com/current-team`, and the URL won't change after switching teams.
## Deep Link Method
The deep link method is generally recommended because it avoids some common issues associated with the current team method. If two users share a link while using deep link URLs, the receiving user will always be directed to the correct team's information based on the link.
## Current Team Method
While the current team method can be simpler to implement, it has a downside. If a user shares a link, the recipient might see information about the wrong team (if their "current team" is set differently). This method can also cause problems when a user has multiple browser tabs open with different teams.
## Selected Team Switcher
To facilitate team selection, Stack provides a component that looks like this:
![TeamSwitcher](../imgs/team-switcher.png)
You can import and use the `SelectedTeamSwitcher` component for the "current team" method. It updates the `selectedTeam` when a user selects a team:
```jsx
import { SelectedTeamSwitcher } from "@stackframe/stack";
export function MyPage() {
return (
<div>
<SelectedTeamSwitcher/>
</div>
);
}
```
To combine the switcher with the deep link method, you can pass in `urlMap` and `selectedTeam`. The `urlMap` is a function to generate a URL based on the team information, and `selectedTeam` is the team that the user is currently working on. This lets you implement "deep link" + "most recent team". The component will update the `user.selectedTeam` with the `selectedTeam` prop:
```jsx
<SelectedTeamSwitcher
urlMap={team => `/team/${team.id}`}
selectedTeam={team}
/>
```
To implement the "deep link" + "default team" method, where you update the `selectedTeam` only when the user clicks "set to default team" or similar, pass `noUpdateSelectedTeam`:
```jsx
<SelectedTeamSwitcher
urlMap={team => `/team/${team.id}`}
selectedTeam={team}
noUpdateSelectedTeam
/>
```
## Example: Deep Link + Most Recent Team
First, create a page at `/app/team/[teamId]/page.tsx` to display information about a specific team:
```jsx title="/app/team/[teamId]/page.tsx"
"use client";
import { useUser, SelectedTeamSwitcher } from "@stackframe/stack";
export default function TeamPage({ params }: { params: { teamId: string } }) {
const user = useUser({ or: 'redirect' });
const team = user.useTeam(params.teamId);
if (!team) {
return <div>Team not found</div>;
}
return (
<div>
<SelectedTeamSwitcher
urlMap={team => `/team/${team.id}`}
selectedTeam={team}
/>
<p>Team Name: {team.displayName}</p>
<p>You are a member of this team.</p>
</div>
);
}
```
Next, create a page to display all teams at `/app/team/page.tsx`:
```jsx title="/app/team/page.tsx"
"use client";
import { useRouter } from "next/navigation";
import { useUser } from "@stackframe/stack";
export default function TeamsPage() {
const user = useUser({ or: 'redirect' });
const teams = user.useTeams();
const router = useRouter();
return (
<div>
{user.selectedTeam &&
<button onClick={() => router.push(`/team/${user.selectedTeam.id}`)}>
Most recent team
</button>
}
<h2>All Teams</h2>
{teams.map(team => (
<button key={team.id} onClick={() => router.push(`/team/${team.id}`)}>
Open {team.displayName}
</button>
))}
</div>
);
}
```
Now, if you navigate to `http://localhost:3000/team`, you should be able to see and interact with the teams.

View File

@ -0,0 +1,154 @@
---
slug: getting-started/teams
subtitle: Manage teams and team members
---
Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams, which can represent departments, B2B customers, or projects.
To assign users to a default team upon sign-up, activate the corresponding toggle in the Stack dashboard under the team settings tab. This setting automatically assigns each new user to a team.
## Creating a Team
To create a team, use the `createTeam` method on the `stackServerApp`. Heres an example:
```tsx title="Create a Team"
const team = await stackServerApp.createTeam({
displayName: 'New Team',
});
```
## Adding a User to a Team
To add a user to a team, use the `addUser` method on the `Team` object. Heres how:
```tsx title="Add a User to a Team"
const team = await stackServerApp.getTeam('teamId');
await team.addUser(user);
```
## List All the Teams of a User
You can list all the teams a user belongs to by using the `listTeams` method or the `useTeams` hook on the User object. Here's how to do it:
<Tabs>
<Tab title="Client Component">
```tsx title="List teams of a user on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserTeams() {
const user = useUser({ or: 'redirect' });
const teams = user.useTeams();
return (
<div>
{teams.map(team => (
<div key={team.id}>{team.displayName}</div>
))}
</div>
);
}
```
</Tab>
<Tab title="Server Component">
```tsx title="List teams of a user on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserTeams() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const teams = await user.listTeams();
return (
<div>
{teams.map(team => (
<div key={team.id}>{team.displayName}</div>
))}
</div>
);
}
```
</Tab>
</Tabs>
## Get Specific Team of a User
To obtain details of a specific team a user belongs to, use the `getTeam` method or `useTeam` hook. Note: this might return `null` if the user is not a member of that team. Heres an example:
<Tabs>
<Tab title="Client Component">
```tsx title="Get a specific team of a user on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserTeam(props: { teamId: string }) {
const user = useUser({ or: 'redirect' });
const team = user.useTeam(props.teamId);
return (
<div>
{team ? team.displayName : 'Not a member of this team'}
</div>
);
}
```
</Tab>
<Tab title="Server Component">
```tsx title="Get a specific team of a user on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserTeam(props: { teamId: string }) {
const user = await stackServerApp.getUser({ or: 'redirect' });
const team = await user.getTeam(props.teamId);
return (
<div>
{team ? team.displayName : 'Not a member of this team'}
</div>
);
}
```
</Tab>
</Tabs>
## List All the Teams
To list all teams, use the `listTeams` method on the `stackServerApp`. Heres an example:
```tsx
const teams = await stackServerApp.listTeams();
```
## Update a Team
To update a team, use the `update` method on a server-side team object. Heres how to do it:
```tsx
const team = await stackServerApp.getTeam('teamId');
await team.update({
displayName: 'New Team Name',
});
```
## Remove a User from a Team
To remove a user from a team, use the `removeUser` method on the `team` object. Here's an example:
```tsx
const team = await stackServerApp.getTeam(teamId);
await team?.removeUser(userId);
```
## List All the Members of a Team
To list all the members of a team, use the `listMembers` method on a server-side team object. Note that the returned result is a list of `TeamMember` objects, from which you can also get the user object. Heres an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const members = await team.listMembers();
for (const member of members) {
const user = member.user;
console.log(`${user.displayName} is a member of ${team.displayName}`);
}
```

View File

@ -14,10 +14,12 @@ For example, if you want to create a custom sign-in page with a customized title
import { SignIn } from "@stackframe/stack";
export default function CustomSignInPage() {
return <div>
<h1>My Custom Sign In page</h1>
<SignIn />
</div>;
return (
<div>
<h1>My Custom Sign In page</h1>
<SignIn />
</div>
);
}
```
@ -49,15 +51,17 @@ import { useStackApp } from "@stackframe/stack";
export default function CustomOAuthSignIn() {
const app = useStackApp();
return <div>
<h1>My Custom Sign In page</h1>
<button onClick={async () => {
// this will redirect to the OAuth provider's login page
await app.signInWithOAuth('google');
}}>
Sign In with Google
</button>
</div>;
return (
<div>
<h1>My Custom Sign In page</h1>
<button onClick={async () => {
// this will redirect to the OAuth provider's login page
await app.signInWithOAuth('google');
}}>
Sign In with Google
</button>
</div>
);
}
```

View File

@ -37,15 +37,17 @@ import { useStackApp } from "@stackframe/stack";
export default function CustomOAuthSignIn() {
const app = useStackApp();
return <div>
<h1>My Custom Sign In page</h1>
<button onClick={async () => {
// this will redirect to the OAuth provider's login page
await app.signInWithOAuth('google');
}}>
Sign In with Google
</button>
</div>;
return (
<div>
<h1>My Custom Sign In page</h1>
<button onClick={async () => {
// this will redirect to the OAuth provider's login page
await app.signInWithOAuth('google');
}}>
Sign In with Google
</button>
</div>
);
}
```

View File

@ -4,50 +4,50 @@ subtitle: Welcome to Stack!
---
<CardGroup>
<Card
title="Setup Guide"
icon="fa-regular fa-play"
href="/getting-started/setup"
>
Setup Stack in your project
</Card>
<Card
title="SDK Reference"
icon="fa-regular fa-file-lines"
href="/sdk"
>
Learn how to use Stack's SDKs for Next.js
</Card>
<Card
title="REST API Reference"
icon="fa-solid fa-code"
href="/rest-api"
>
Explore Stack's REST API for frameworks that aren't natively supported yet
</Card>
<Card
title="Discord"
icon="fa-brands fa-discord"
href="https://discord.stack-auth.com"
>
Join our Discord community
</Card>
<Card
title="Setup Guide"
icon="fa-regular fa-play"
href="/getting-started/setup"
>
Setup Stack in your project
</Card>
<Card
title="SDK Reference"
icon="fa-regular fa-file-lines"
href="/sdk"
>
Learn how to use Stack's SDKs for Next.js
</Card>
<Card
title="REST API Reference"
icon="fa-solid fa-code"
href="/rest-api"
>
Explore Stack's REST API for frameworks we don't natively support yet
</Card>
<Card
title="Discord"
icon="fa-brands fa-discord"
href="https://discord.stack-auth.com"
>
Join our Discord community
</Card>
</CardGroup>
## Why Stack?
## Why Choose Stack?
Authentication is inherently difficult. There are only few things more sensitive than user data, and only few things more difficult than cryptography. It's not surprising that a majority of online businesses struggle to get it right.
Authentication is inherently difficult. Few things are more sensitive than user data and more complex than cryptography. Not surprisingly, many online businesses struggle to get it right.
The optimal authentication solution is secure, yet approachable. If a developer has to worry about JWTs, OAuth flows, or password hashing, then we have failed. If an authentication solution uses closed-source, unauditable code for the most critical parts of your application, then we have failed.
The optimal authentication solution should be secure, yet approachable. If a developer has to worry about JWTs, OAuth flows, or password hashing, then we have failed. If an authentication solution uses closed-source, unauditable code for the most critical parts of your application, then we have failed.
The truth is; as the authentication services industry, we have collectively failed. It is dominated by proprietary giants with predatory "bait-and-switch" pricing who provide no transparency into their codebase, and a terrible developer experience because they have determined that enterprises are willing to pay more if setting up auth systems is painful.
In truth, the authentication services industry has collectively failed. It's dominated by proprietary giants with predatory "bait-and-switch" pricing, providing no transparency into their codebase and delivering a subpar developer experience — because they know enterprises will pay more if setting up auth systems is painful.
That's why we built Stack. Integrating secure authentication into your app should be a matter of **5 minutes**, rather than 5 days.
That's why we built Stack. Integrating secure authentication into your app should take **5 minutes**, not 5 days.
At the core of this are deep integrations into frontend and backend frameworks. We give the best developer experience to anyone using our supported tech stacks; at the moment, this is Next.js with Postgres and TypeScript or Python backends. Instead of giving mediocre support for a lot of frameworks, we chose to make a few integrations as excellent as possible before adding new ones (though we do offer a cross-compatible REST API as a fallback).
At the core of Stack are deep integrations into frontend and backend frameworks. We offer the best developer experience to those using our supported tech stacks — currently, Next.js with Postgres and TypeScript or Python backends. Instead of providing mediocre support for numerous frameworks, we focused on making a few integrations excellent before adding new ones. We also offer a cross-compatible REST API as a fallback.
Heres an example. To retrieve the current user, simply call:
Here is an example. To retrieve the current user, simply call:
```tsx
export function MyComponent() {
const user = useUser({ or: "redirect" });
@ -64,24 +64,27 @@ You can also add a button to change the user's name:
Change Name
</button>
```
The user data will update in both the frontend and backend automatically. The updated user data will be reflected in all other components on your page as well.
You also get pages and components for authentication flow out-of-the-box. This is the sign-in page that you get without writing a single line of code:
You also get pages and components for the authentication flow out-of-the-box. This is the sign-in page you get without writing a single line of code:
![Stack sign up page](../imgs/signup-page.png)
Notably, there's no branding on any of our components. We believe that we should grow by building the best product, not by forcing our brand on your users — but this means that we **rely on you to spread the word about Stack**. If you like what you're reading, we'd love if you could take a second to tell one or two of your friends about us.
Notice, there's no branding on our components. We believe we should grow by building the best product, not by forcing our brand onto your users. This means we **rely on you to spread the word about Stack**. If you like what youre reading, please take a moment to tell one or two of your friends about us.
If you prefer a fully customized UI, you can use our low-level functions like `signInWithOAuth` or `signInWithCredential` to build your own sign-in page:
```tsx
export default function CustomOAuthSignIn() {
const app = useStackApp();
return <div>
<button onClick={async () => await app.signInWithOAuth('google')}>
Sign In with Google
</button>
</div>;
return (
<div>
<button onClick={async () => await app.signInWithOAuth('google')}>
Sign In with Google
</button>
</div>
);
}
```
@ -89,10 +92,10 @@ To manage everything efficiently, there is a powerful admin dashboard:
![Stack dashboard](../imgs/dashboard.png)
Best of all, Stack is **100% open-source**. That means client, server, dashboard, and even this very documentation you're reading right now. Feel free to check out our [GitHub](https://github.com/stack-auth/stack) and open an issue or pull request.
Best of all, Stack is **100% open-source**. This means the client, server, dashboard, and even this documentation youre reading right now. Check out our [GitHub](https://github.com/stack-auth/stack) to open an issue or pull request.
This is just a glimpse of what Stack can do. Stack also handles many other tasks like backend integration, data storage, emails, teams, permissions, and more, which you will learn later in the documentation.
This is just a glimpse of what Stack can do. Stack also handles many other tasks like backend integration, data storage, emails, teams, permissions, and more, which you will learn about later in the documentation.
If this sounds interesting, [get started](../getting-started/setup.mdx) with our interactive setup wizard, or join [our Discord community](https://discord.stack-auth.com) to ask questions and get help from our team.
We're excited to have you on board! 🚀
We're excited to have you on board! 🚀

View File

@ -0,0 +1,60 @@
---
slug: getting-started/production
subtitle: Steps to Prepare Stack for Production Use
---
Stack makes development easy with various default settings, but these settings need to be optimized for security and user experience when moving to production. Here's a checklist of things you need to do before switching to production mode:
### Domains and Handlers
By default, Stack allows all localhost paths as valid callback URLs. This is convenient for development but poses a security risk in production because attackers could use their own domains as callback URLs to intercept sensitive information. Therefore, in production, Stack must know your domain (e.g., `https://your-website.com`) and only allow callbacks from those domains.
Follow these steps when you're ready to push your application to production:
1. **Add Your Domain**: Navigate to the `Domain & Handlers` tab in the Stack dashboard. If you haven't configured your handler, you can leave it as the default. (Learn more about handlers [here](../sdk/app.mdx)).
2. **Disable Localhost Callbacks**: For enhanced security, disable the `Allow all localhost callbacks for development` option.
### OAuth Providers
Stack uses shared OAuth keys for development to simplify setup when using "Sign in with Google/GitHub/etc." However, this isn't secure for production as it displays "Stack Development" on the providers' consent screens, making it unclear to users if the OAuth request is genuinely from your site. Thus, you should configure your own OAuth keys with the providers and connect them to Stack.
To use your own OAuth provider setups in production, follow these steps for each provider you use:
1. **Create an OAuth App**: On the provider's website, create an OAuth app and set the callback URL to the corresponding Stack callback URL. Copy the client ID and client secret.
<Tabs>
<Tab title="Google">
[Google OAuth Setup Guide](https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name-.)
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/google`
</Tab>
<Tab title="GitHub">
[GitHub OAuth Setup Guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/github`
</Tab>
<Tab title="Facebook">
[Facebook OAuth Setup Guide](https://developers.facebook.com/docs/development/create-an-app/facebook-login-use-case)
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/facebook`
</Tab>
<Tab title="Microsoft">
[Microsoft Azure OAuth Setup Guide](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/microsoft`
</Tab>
<Tab title="Spotify">
[Spotify OAuth Setup Guide](https://developer.spotify.com/documentation/general/guides/app-settings/)
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/spotify`
</Tab>
</Tabs>
2. **Enter OAuth Credentials**: Go to the `Auth Methods` section in the Stack dashboard, open the provider's settings, switch from shared keys to custom keys, and enter the client ID and client secret.
### Email Server
For development, Stack uses a shared email server, which sends emails from Stack's domain. This is not ideal for production as users may not trust emails from an unfamiliar domain. You should set up an email server connected to your own domain.
Steps to connect your own email server with Stack:
1. **Setup Email Server**: Configure your own email server and connect it to your domain (this step is beyond Stack's documentation scope).
2. **Configure Stack's Email Settings**: Navigate to the `Emails` section in the Stack dashboard, click `Edit` in the `Email Server` section, switch from `Shared` to `Custom SMTP server`, enter your SMTP configurations, and save.
### Enable Production Mode
After completing the steps above, you can enable production mode on the `Project Settings` tab in the Stack dashboard, ensuring that your website runs securely with Stack in a production environment.

View File

@ -1,5 +1,6 @@
---
slug: getting-started/setup
subtitle: How to install stack to your Next.js project
---
## Setup

View File

@ -1,248 +0,0 @@
---
slug: getting-started/teams
---
Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams, which can represent departments, B2B customers, or projects.
To assign users to a default team upon sign-up, activate the corresponding toggle in the Stack dashboard under the team settings tab. This setting automatically assigns each new user to a team.
## Teams
### Creating a Team
To create a team, you can call the `createTeam` method on the `stackServerApp`. Here is an example:
```tsx title="Create a Team"
const team = await stackServerApp.createTeam({
displayName: 'New Team',
});
```
### Adding a User to a Team
To add a user to a team, you can call the `addUser` method on the `Team` object. Here is an example:
```tsx title="Add a User to a Team"
const team = await stackServerApp.getTeam('teamId');
await team.addUser(user);
```
### List All the Teams of a User
You can list all the teams a user belongs to by using the `listTeams` method or `useTeams` hook on the User object. Here is how you can do it:
<Tabs>
<Tab title="Client Component">
```tsx title="List teams of a user on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserTeams() {
const user = useUser({ or: 'redirect' });
const teams = user.useTeams();
return <div>
{teams.map(team => <div key={team.id}>{team.displayName}</div>)}
</div>;
}
```
</Tab>
<Tab title="Server Component">
```tsx title="List teams of a user on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserTeams() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const teams = await user.listTeams();
return <div>
{teams.map(team => <div key={team.id}>{team.displayName}</div>)}
</div>;
}
```
</Tab>
</Tabs>
### Get Specific Team of a User
To obtain details of a specific team that a user belongs to, use the `getTeam` method or `useTeam` hook. Note, this may return `null` if the user is not a member of the team. Here is an example:
<Tabs>
<Tab title="Client Component">
```tsx title="Get a specific team of a user on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserTeam(props: { teamId: string }) {
const user = useUser({ or: 'redirect' });
const team = user.useTeam(props.teamId);
return <div>
{team ? team.displayName : 'Not a member of this team'}
</div>;
}
```
</Tab>
<Tab title="Server Component">
```tsx title="Get a specific team of a user on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserTeam(props: { teamId: string }) {
const user = await stackServerApp.getUser({ or: 'redirect' });
const team = await user.getTeam(props.teamId);
return <div>
{team ? team.displayName : 'Not a member of this team'}
</div>;
}
```
</Tab>
</Tabs>
### List All the Teams
To list all the teams, you can call the `listTeams` method on the `stackServerApp`, this is an example:
```tsx
const teams = await stackServerApp.listTeams();
```
### Update a Team
To update a team, use the `update` method on a server-side team object. Here is an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
await team.update({
displayName: 'New Team Name',
});
```
### Remove a User from a Team
To remove a user from a team, you can call the `removeUser` method on the `team`, this is an example:
```tsx
const team = await stackServerApp.getTeam(teamId);
await team?.removeUser(userId);
```
### List All the Members of a Team
To list all the members of a team, you can call the `listMembers` method on a server side team object. Note that the returned result is a list of `TeamMember` objects, from which you can also get the user object. This is an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const members = await team.listMembers();
for (const member of members) {
const user = member.user;
console.log(user.displayName, 'is a member of', team.displayName);
}
```
## Permissions
Permissions control what each user can do within your application. Create permissions on the Stack dashboard and assign them to users. You can then verify in your app whether a user has a specific permission.
Permissions can be nested, allowing you to create a hierarchical structure. For instance, an `admin` permission can include `moderator` and `user` permissions. We provide tools to help you verify whether a user has a permission directly or indirectly.
### Check if a User has a Permission
You can check if a user has a specific permission by using the `getPermission` method or `usePermission` hook on the `User` object. This returns the `Permission` object if the user has it; otherwise, it returns `null`. Be cautious with client-side checks for permissions, as they can be bypassed. Always perform permission checks on the server side for business logic. Here is an example:
<Tabs>
<Tab title="Client Component">
```tsx title="Check user permission on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function CheckUserPermission() {
const user = useUser({ or: 'redirect' });
const permission = user.usePermission('read');
// Don't rely on client side permission checks for business logic
return <div>
{permission ? 'You have the read permission' : 'You shall not pass'}
</div>;
}
```
</Tab>
<Tab title="Server Component">
```tsx title="Check user permission on the server"
import { stackServerApp } from "@/stack";
export default async function CheckUserPermission() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const permission = await user.getPermission('read');
// This is a server side check, so it's secure
return <div>
{permission ? 'You have the read permission' : 'You shall not pass'}
</div>;
}
```
</Tab>
</Tabs>
### List All the Permissions of a User
To get all permissions a user has, use the `listPermissions` method or `usePermissions` hook on the User object. This method retrieves all direct and indirect permissions. Here is an example:
<Tabs>
<Tab title="Client Component" default>
```tsx title="List user permissions on the client"
"use client";
import { useUser } from "@stackframe/stack";
export function DisplayUserPermissions() {
const user = useUser({ or: 'redirect' });
const permissions = user.usePermissions();
return <div>
{permissions.map(permission => <div key={permission.id}>{permission.id}</div>)}
</div>;
}
```
</Tab>
<Tab title="Server Component">
```tsx title="List user permissions on the server"
import { stackServerApp } from "@/stack";
export default async function DisplayUserPermissions() {
const user = await stackServerApp.getUser({ or: 'redirect' });
const permissions = await user.listPermissions();
return <div>
{permissions.map(permission => <div key={permission.id}>{permission.id}</div>)}
</div>;
}
```
</Tab>
</Tabs>
### Grant a Permission to a User
To grant a permission to a user, you can call the `grantPermission` method on the `ServerUser`. Here is an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const user = await stackServerApp.getUser();
await user.grantPermission(team, 'read');
```
### Revoke a Permission from a User
To revoke a permission from a user, you can call the `revokePermission` method on the `ServerUser`. Here is an example:
```tsx
const team = await stackServerApp.getTeam('teamId');
const user = await stackServerApp.getUser();
await user.revokePermission(team, 'read');
```

View File

@ -1,5 +1,6 @@
---
slug: getting-started/users
subtitle: Get and manage user information
---
In [the last guide](./setup.mdx), we initialized Stack. This created new files containing a `StackServerApp` and `StackProvider`. In this section, we will show you how to utilize those for accessing and modifying the current user information on Server Components and Client Components, respectively.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,5 +1,5 @@
'use client';
import { useUser } from "..";
import { Team, useUser } from "..";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { useRouter } from "next/navigation";
import {
@ -12,15 +12,17 @@ import {
SelectValue,
Typography,
} from "@stackframe/stack-ui";
import { useMemo } from "react";
import { useEffect, useMemo } from "react";
type SelectedTeamSwitcherProps = {
projectUrlMap?: (projectId: string) => string,
urlMap?: (projectId: string) => string,
selectedTeam?: Team,
noUpdateSelectedTeam?: boolean,
};
function TeamIcon(props: { displayName: string }) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '1.5rem', height: '1.5rem', marginRight: '0.5rem', borderRadius: '0.25rem', backgroundColor: 'rgb(228 228 231)' }}>
<div className="flex items-center justify-center w-6 h-6 mr-2 rounded bg-gray-200">
<Typography>{props.displayName.slice(0, 1).toUpperCase()}</Typography>
</div>
);
@ -29,10 +31,16 @@ function TeamIcon(props: { displayName: string }) {
export function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {
const user = useUser();
const router = useRouter();
const selectedTeam = user?.selectedTeam;
const selectedTeam = user?.selectedTeam || props.selectedTeam;
const rawTeams = user?.useTeams();
const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);
useEffect(() => {
if (!props.noUpdateSelectedTeam && teams && selectedTeam && !teams.find(team => team.id === selectedTeam.id)) {
runAsynchronouslyWithAlert(user?.setSelectedTeam(selectedTeam));
}
}, [teams, selectedTeam, props.noUpdateSelectedTeam]);
return (
<Select>
<SelectTrigger className="stack-scope">
@ -45,9 +53,11 @@ export function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {
key={team.id}
onClick={() => {
runAsynchronouslyWithAlert(async () => {
await user?.setSelectedTeam(team);
if (props.projectUrlMap) {
router.push(props.projectUrlMap(team.id));
if (!props.noUpdateSelectedTeam) {
await user?.setSelectedTeam(team);
}
if (props.urlMap) {
router.push(props.urlMap(team.id));
}
});
}}