mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-03 21:02:05 +08:00
331 lines
11 KiB
Plaintext
331 lines
11 KiB
Plaintext
---
|
|
title: User Fundamentals
|
|
description: Access and manage user information within custom components
|
|
sidebarTitle: User Fundamentals
|
|
---
|
|
|
|
import { UserFieldsTable } from "/snippets/user-fields-table.jsx";
|
|
|
|
You've set up Hexclave. Now let's understand the most important object in your application: the **User**.
|
|
|
|
The user object represents whoever is currently interacting with your app — their identity, profile, and metadata. Almost everything you build will revolve around it: retrieving the current user, protecting pages from unauthorized access, updating profile information, signing out, and more.
|
|
|
|
## Getting the current user
|
|
|
|
On both client and server, you can use the `getUser()` function to get the current user (or `null` if not signed in). In React apps, there is also a `useUser()` hook which automatically updates when the value changes.
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser();
|
|
if (user) {
|
|
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
|
|
} else {
|
|
console.log("Not signed in");
|
|
}
|
|
```
|
|
|
|
```tsx my-react-component.tsx
|
|
"use client";
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
// Like most `getXyz()` or `listXyz()` functions, Hexclave provides a `useUser()` hook equivalent to `getUser()`.
|
|
// It behaves the same, but returns the user directly instead of a Promise, and updates when the user object changes.
|
|
// Note: In Server Components, you can still use `await hexclaveServerApp.getUser()`.
|
|
export default async function MyReactComponent() {
|
|
const user = hexclaveClientApp.useUser();
|
|
if (user) {
|
|
return <div>Hello, {user.displayName ?? user.primaryEmail ?? "anon"}</div>;
|
|
} else {
|
|
return <div>You are not logged in</div>;
|
|
}
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
### Protecting a page & requiring a signed-in user
|
|
|
|
Sometimes, you want to retrieve the user only if they're signed in, and redirect to the sign-in page otherwise. In this case, simply pass `{ or: "redirect" }`, and the function will never return `null`.
|
|
|
|
You can also use `{ or: "throw" }` to throw an error instead — useful in API routes and server actions where a redirect doesn't make sense.
|
|
|
|
In both cases, the return type is non-nullable, so you don't need to handle `null`.
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser({ or: "redirect" });
|
|
// user is guaranteed to be non-null here
|
|
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
|
|
```
|
|
|
|
```tsx my-react-component.tsx
|
|
"use client";
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
export default function MyReactComponent() {
|
|
const user = hexclaveClientApp.useUser({ or: "redirect" });
|
|
// user is guaranteed to be non-null here
|
|
return <div>{`Hello, ${user.displayName ?? user.primaryEmail ?? "anon"}`}</div>;
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
## User data
|
|
|
|
### What's on a user object?
|
|
|
|
Before diving into updates, here's an overview of the most important fields available on every user:
|
|
|
|
<UserFieldsTable />
|
|
|
|
For the full list of fields and methods, see the [User SDK reference](/sdk/types/user).
|
|
|
|
### Updating a user
|
|
|
|
You can update attributes on a user object with the `user.update()` function.
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser();
|
|
await user.update({ displayName: "New Name" });
|
|
```
|
|
|
|
```tsx my-client-component.tsx
|
|
'use client';
|
|
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
|
|
|
|
export default function MyClientComponent() {
|
|
const user = useUser();
|
|
return <button onClick={async () => await user.update({ displayName: "New Name" })}>
|
|
Change Name
|
|
</button>;
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
### Custom metadata
|
|
|
|
Beyond built-in fields like `displayName` and `primaryEmail`, you can store your own JSON-like data on a user. Hexclave provides three metadata fields, each with different read and write permissions:
|
|
|
|
| Field | Client access | Server access | Use for |
|
|
| --- | --- | --- | --- |
|
|
| `clientMetadata` | Read and write | Read and write | Non-sensitive user preferences, such as theme, locale, onboarding form drafts, or UI state. |
|
|
| `serverMetadata` | No access | Read and write | Sensitive or internal data that users should not be able to see or modify. |
|
|
| `clientReadOnlyMetadata` | Read only | Read and write | Server-authoritative data that the client should display, such as subscription plans, role labels, or validated onboarding state. |
|
|
|
|
Use `clientMetadata` for information that is safe for the browser to read and change. For example, a user can store their own theme preference:
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser();
|
|
await user.update({
|
|
clientMetadata: {
|
|
theme: "dark",
|
|
},
|
|
});
|
|
```
|
|
|
|
```tsx my-client-component.tsx
|
|
'use client';
|
|
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
|
|
|
|
export default function ThemeToggle() {
|
|
const user = useUser({ or: "redirect" });
|
|
const currentTheme = user.clientMetadata?.theme ?? "light";
|
|
|
|
return (
|
|
<button onClick={async () => {
|
|
await user.update({
|
|
clientMetadata: {
|
|
...user.clientMetadata,
|
|
theme: currentTheme === "light" ? "dark" : "light",
|
|
},
|
|
});
|
|
}}>
|
|
Switch to {currentTheme === "light" ? "dark" : "light"} mode
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
Use `serverMetadata` for sensitive or internal data. It is only available through [`HexclaveServerApp`](/sdk/objects/hexclave-app#hexclaveserverapp):
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-server-file.ts
|
|
import { hexclaveServerApp } from "../src/stack/server";
|
|
|
|
const user = await hexclaveServerApp.getUser();
|
|
await user.update({
|
|
serverMetadata: {
|
|
internalFlag: true,
|
|
},
|
|
});
|
|
|
|
console.log(user.serverMetadata);
|
|
```
|
|
</CodeGroup>
|
|
|
|
Use `clientReadOnlyMetadata` when the client needs to read a value but only your server should decide it. A common example is storing a validated onboarding or subscription state:
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-server-file.ts
|
|
import { hexclaveServerApp } from "../src/stack/server";
|
|
|
|
const user = await hexclaveServerApp.getUser({ or: "throw" });
|
|
await user.update({
|
|
clientReadOnlyMetadata: {
|
|
subscriptionPlan: "premium",
|
|
onboarded: true,
|
|
},
|
|
});
|
|
```
|
|
|
|
```tsx my-client-component.tsx
|
|
'use client';
|
|
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
|
|
|
|
export default function SubscriptionBadge() {
|
|
const user = useUser({ or: "redirect" });
|
|
const plan = user.clientReadOnlyMetadata?.subscriptionPlan ?? "free";
|
|
|
|
return <div>Current plan: {plan}</div>;
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
<Warning>
|
|
Do not put secrets or trusted authorization decisions in `clientMetadata`. Users can read and modify it from client-side code. If the value affects access control, validate it on your server and store it in `serverMetadata` or `clientReadOnlyMetadata`.
|
|
</Warning>
|
|
|
|
## Signing out
|
|
|
|
You can sign out the user by calling `user.signOut()`. They will be redirected to the URL [configured as `afterSignOut` in the Hexclave App](/sdk/objects/hexclave-app).
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser();
|
|
await user.signOut();
|
|
```
|
|
|
|
```tsx my-client-component.tsx
|
|
'use client';
|
|
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
|
|
|
|
export default function MyClientComponent() {
|
|
const user = useUser();
|
|
return <button onClick={() => user.signOut()}>Sign Out</button>;
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
## Example: Custom profile page
|
|
|
|
Hexclave automatically creates a user profile on sign-up. Let's build a page that displays this information. In `app/profile/page.tsx`:
|
|
|
|
<CodeGroup dropdown>
|
|
```tsx my-app.tsx
|
|
"use client";
|
|
import { useUser, useHexclaveApp, UserButton } from "../src/stack/client";
|
|
|
|
export default function PageClient() {
|
|
const user = useUser();
|
|
const app = useHexclaveApp();
|
|
return (
|
|
<div>
|
|
{user ? (
|
|
<div>
|
|
<UserButton />
|
|
<p>Welcome, {user.displayName ?? "unnamed user"}</p>
|
|
<p>Your e-mail: {user.primaryEmail}</p>
|
|
<button onClick={() => user.signOut()}>Sign Out</button>
|
|
</div>
|
|
) : (
|
|
<div>
|
|
<p>You are not logged in</p>
|
|
<button onClick={() => app.redirectToSignIn()}>Sign in</button>
|
|
<button onClick={() => app.redirectToSignUp()}>Sign up</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
```tsx my-client-component.tsx
|
|
"use client";
|
|
import { hexclaveClientApp, UserButton } from "../src/stack/client";
|
|
|
|
export default function MyClientComponent() {
|
|
const user = hexclaveClientApp.useUser();
|
|
const app = hexclaveClientApp.useHexclaveApp();
|
|
return (
|
|
<div>
|
|
{user ? (
|
|
<div>
|
|
<UserButton />
|
|
<p>Welcome, {user.displayName ?? "unnamed user"}</p>
|
|
<p>Your e-mail: {user.primaryEmail}</p>
|
|
<p><button onClick={() => user.signOut()}>Sign Out</button></p>
|
|
</div>
|
|
) : (
|
|
<div>
|
|
<p>You are not logged in</p>
|
|
<p><button onClick={() => app.redirectToSignIn()}>Sign in</button></p>
|
|
<p><button onClick={() => app.redirectToSignUp()}>Sign up</button></p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
After saving your code, you can see the profile page on [http://localhost:3000/profile](http://localhost:3000/profile).
|
|
|
|
For more examples on how to use the `User` object, check the [the SDK documentation](/sdk/types/user).
|
|
|
|
## Anonymous users
|
|
|
|
Hexclave supports anonymous users - users who can interact with your app without signing up. This is useful for features like guest checkouts, try-before-you-sign-up flows, or collecting analytics before a user creates an account. If you have the analytics app enabled, anonymous users will automatically be created and visible in the Users table.
|
|
|
|
In most ways, anonymous users are just like any other, however, there are some key differences:
|
|
|
|
- Unless opted in, anonymous users cannot access any backend functions that require a signed-in user.
|
|
- By default, anonymous users are not returned by `getUser()`, `useUser()`.
|
|
- Anonymous users have a different set of [JWT keys](/guides/apps/authentication/jwts), which means they cannot access protected routes or API endpoints.
|
|
- Anonymous users are always [restricted](/guides/apps/authentication/restricted-users).
|
|
|
|
To get an anonymous user, pass `{ or: "anonymous" }` to `useUser()` or `getUser()`. This will create an anonymous user if the user isn't signed in.
|
|
|
|
<CodeGroup dropdown>
|
|
```ts my-app.ts
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
const user = await hexclaveClientApp.getUser({ or: "anonymous" });
|
|
// user is guaranteed to be non-null here
|
|
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
|
|
```
|
|
|
|
```tsx my-client-component.tsx
|
|
"use client";
|
|
import { hexclaveClientApp } from "../src/stack/client";
|
|
|
|
export default function MyComponent() {
|
|
const user = useUser({ or: "anonymous" });
|
|
// user is always non-null — either a real user or an anonymous one
|
|
return <div>Your user ID: {user.id}</div>;
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
Anonymous users have an `isAnonymous` property set to `true`.
|