mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Add CLI options for project ID and publishable client key, update initialization process, and modify documentation to reflect changes. > > - **CLI Options**: > - Added `--project-id` and `--publishable-client-key` options to `index.ts` for CLI setup. > - **Initialization**: > - Updated `writeEnvVars()` in `index.ts` to include project ID and publishable client key in `.env.local`. > - Modified `writeStackAppFile()` in `index.ts` to handle new CLI options. > - **Documentation**: > - Updated references from `stack.ts` to `stack/client.ts` and `stack/server.ts` in multiple `.mdx` files. > - Added examples for using project ID and publishable client key in `setup.mdx` and `example-pages.mdx`. > - **Testing**: > - Added `test-run-keys-next` and `test-run-keys-js` scripts in `package.json` for testing new CLI options. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup> forb204910ebd. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> ---- <!-- ELLIPSIS_HIDDEN --> <!-- RECURSEML_SUMMARY:START --> ## Review by RecurseML _🔍 Review performed on [bd14f6b..92c332a](bd14f6be6a...92c332ad3f)_ ✨ No bugs found, your code is sparkling clean <details> <summary>✅ Files analyzed, no issues (2)</summary> • `packages/init-stack/src/index.ts` • `apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx` </details> <details> <summary>⏭️ Files skipped (trigger manually) (1)</summary> | Locations | Trigger Analysis | |-----------|------------------| `packages/init-stack/package.json` | [ </details> [](https://discord.gg/n3SsVDAW6U) <!-- RECURSEML_SUMMARY:END --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Init tool accepts project ID and publishable client key; generated projects include those values in client/server outputs and env hints. - Next.js projects now generate both client and server app artifacts using a standardized client/server layout. - UI - Removed the vertical divider on the New Project page for a cleaner preview/form layout. - Documentation - Updated docs and examples to reference the new client/server file split. - Chores - Added key-based test-run scripts for Next.js and JS. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
456 lines
13 KiB
Plaintext
456 lines
13 KiB
Plaintext
---
|
|
title: Example-pages
|
|
---
|
|
|
|
|
|
This guide demonstrates how to integrate Stack Auth with Vite. The same principles apply to other JavaScript frameworks as well. You can find the complete example code in our [GitHub repository](https://github.com/stack-auth/stack-auth/tree/main/examples/js-example).
|
|
|
|
### Initialize the app
|
|
|
|
```typescript title="stack/client.ts"
|
|
import { StackClientApp } from "@stackframe/js";
|
|
|
|
// Add type declaration for Vite's import.meta.env
|
|
declare global {
|
|
interface ImportMeta {
|
|
env: {
|
|
VITE_STACK_API_URL: string;
|
|
VITE_STACK_PROJECT_ID: string;
|
|
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
|
|
};
|
|
}
|
|
}
|
|
|
|
export const stackClientApp = new StackClientApp({
|
|
baseUrl: import.meta.env.VITE_STACK_API_URL,
|
|
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
|
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
|
tokenStore: "cookie",
|
|
urls: {
|
|
oauthCallback: window.location.origin + "/oauth",
|
|
},
|
|
});
|
|
```
|
|
|
|
### Index page with user information
|
|
|
|
<Tabs defaultValue="html">
|
|
<TabsList>
|
|
<TabsTrigger value="html">index.html</TabsTrigger>
|
|
<TabsTrigger value="script">index-script.ts</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="html">
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Stack Auth JS Examples</title>
|
|
</head>
|
|
<body>
|
|
<h1>Stack Auth JS Examples</h1>
|
|
|
|
<div id="authOptions">
|
|
<p>Choose an authentication example:</p>
|
|
<ul>
|
|
<li><a href="/password-sign-in">Sign in with Password</a></li>
|
|
<li><a href="/password-sign-up">Create Account with Password</a></li>
|
|
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
|
|
<li><a href="/oauth">Sign in with Google</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div id="userInfo" style="display: none;">
|
|
<h2>User Information</h2>
|
|
<p>Email: <span id="userEmail"></span></p>
|
|
<button id="signOut">Sign Out</button>
|
|
</div>
|
|
|
|
<script type="module" src="/index-script.ts"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
</TabsContent>
|
|
|
|
<TabsContent value="script">
|
|
```typescript
|
|
import { stackClientApp } from "./stack/client";
|
|
|
|
const updateUIState = (user: any | null) => {
|
|
const authOptions = document.getElementById("authOptions");
|
|
const userInfo = document.getElementById("userInfo");
|
|
const userEmailSpan = document.getElementById("userEmail");
|
|
|
|
if (user) {
|
|
if (authOptions) authOptions.style.display = "none";
|
|
if (userInfo) userInfo.style.display = "block";
|
|
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
|
} else {
|
|
if (authOptions) authOptions.style.display = "block";
|
|
if (userInfo) userInfo.style.display = "none";
|
|
}
|
|
};
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then(updateUIState);
|
|
|
|
// Handle Sign Out
|
|
document.getElementById("signOut")?.addEventListener("click", async () => {
|
|
const user = await stackClientApp.getUser();
|
|
if (user) {
|
|
await user.signOut();
|
|
updateUIState(null);
|
|
}
|
|
});
|
|
```
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
|
|
### Sign in with password
|
|
|
|
<Tabs defaultValue="html">
|
|
<TabsList>
|
|
<TabsTrigger value="html">password-sign-in.html</TabsTrigger>
|
|
<TabsTrigger value="script">password-sign-in-script.ts</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="html">
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Password Sign In</title>
|
|
</head>
|
|
<body>
|
|
<h1>Password Sign In</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="loginForm">
|
|
<h2>Sign In</h2>
|
|
<input type="email" id="emailInput" placeholder="Email" />
|
|
<input type="password" id="passwordInput" placeholder="Password" />
|
|
<button id="signIn">Sign In</button>
|
|
<div>
|
|
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/password-sign-in-script.ts"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
</TabsContent>
|
|
|
|
<TabsContent value="script">
|
|
```typescript
|
|
import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
document.getElementById("loginForm")?.classList.add("hidden");
|
|
document.getElementById("signUpForm")?.classList.remove("hidden");
|
|
});
|
|
|
|
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
document.getElementById("loginForm")?.classList.remove("hidden");
|
|
document.getElementById("signUpForm")?.classList.add("hidden");
|
|
});
|
|
|
|
document.getElementById("signIn")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign in failed. Please check your email and password and try again.");
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("signUp")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signUpWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign up failed. Please try again.");
|
|
return;
|
|
}
|
|
|
|
const signInResult = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (signInResult.status === "error") {
|
|
alert("Account created but sign in failed. Please sign in manually.");
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
```
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
### Sign up with password
|
|
|
|
<Tabs defaultValue="html">
|
|
<TabsList>
|
|
<TabsTrigger value="html">password-sign-up.html</TabsTrigger>
|
|
<TabsTrigger value="script">password-sign-up-script.ts</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="html">
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Password Sign Up</title>
|
|
</head>
|
|
<body>
|
|
<h1>Password Sign Up</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="signUpForm">
|
|
<h2>Sign Up</h2>
|
|
<input type="email" id="signUpEmail" placeholder="Email" />
|
|
<input type="password" id="signUpPassword" placeholder="Password" />
|
|
<button id="signUp">Sign Up</button>
|
|
<div>
|
|
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/password-sign-up-script.ts"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
</TabsContent>
|
|
|
|
<TabsContent value="script">
|
|
```typescript
|
|
import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("signUp")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signUpWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign up failed. Please try again.");
|
|
return;
|
|
}
|
|
|
|
const signInResult = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (signInResult.status === "error") {
|
|
alert("Account created but sign in failed. Please sign in manually.");
|
|
window.location.href = "/password-sign-in";
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
```
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
### Sign in with OTP/Magic Link
|
|
|
|
<Tabs defaultValue="html">
|
|
<TabsList>
|
|
<TabsTrigger value="html">otp-sign-in.html</TabsTrigger>
|
|
<TabsTrigger value="script">otp-sign-in-script.ts</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="html">
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OTP Sign In</title>
|
|
</head>
|
|
<body>
|
|
<h1>OTP Sign In</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="otpForm">
|
|
<h2>Sign In with Email Code</h2>
|
|
<div id="emailStep">
|
|
<input type="email" id="emailInput" placeholder="Email" />
|
|
<button id="sendCode">Send Code</button>
|
|
</div>
|
|
|
|
<div id="codeStep" style="display: none;">
|
|
<p>Enter the code sent to your email</p>
|
|
<input type="text" id="codeInput" placeholder="Enter code" />
|
|
<button id="verifyCode">Verify Code</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/otp-sign-in-script.ts"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
</TabsContent>
|
|
|
|
<TabsContent value="script">
|
|
```typescript
|
|
import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("signUp")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signUpWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign up failed. Please try again.");
|
|
return;
|
|
}
|
|
|
|
const signInResult = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (signInResult.status === "error") {
|
|
alert("Account created but sign in failed. Please sign in manually.");
|
|
window.location.href = "/password-sign-in";
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
```
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
### OAuth sign in
|
|
|
|
<Tabs defaultValue="html">
|
|
<TabsList>
|
|
<TabsTrigger value="html">oauth.html</TabsTrigger>
|
|
<TabsTrigger value="script">oauth-script.ts</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="html">
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OAuth Authentication</title>
|
|
<style>
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>OAuth Authentication</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="loginButtons">
|
|
<h2>Sign In with OAuth</h2>
|
|
<button id="googleSignIn">Sign in with Google</button>
|
|
</div>
|
|
|
|
<script type="module" src="/oauth-script.ts"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
</TabsContent>
|
|
|
|
<TabsContent value="script">
|
|
```typescript
|
|
import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
// Handle Google Sign In
|
|
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
|
|
try {
|
|
await stackClientApp.signInWithOAuth('google');
|
|
} catch (error) {
|
|
console.error("Google sign in failed:", error);
|
|
alert("Failed to initialize Google sign in");
|
|
}
|
|
});
|
|
|
|
// Handle OAuth redirect
|
|
window.addEventListener("load", async () => {
|
|
try {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const code = params.get("code");
|
|
const state = params.get("state");
|
|
|
|
if (code && state) {
|
|
const user = await stackClientApp.callOAuthCallback();
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to handle OAuth redirect:", error);
|
|
alert("Authentication failed. Please try again.");
|
|
}
|
|
});
|
|
```
|
|
</TabsContent>
|
|
</Tabs>
|