wooden-addition-60810
09/17/2025, 8:48 PM"use client";
import React, { useState, FormEvent } from "react";
import { useSearchParams } from "next/navigation";
import { Button } from "@workspace/ui/components/button";
import { Input } from "@workspace/ui/components/input";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card";
import { Alert, AlertDescription } from "@workspace/ui/components/alert";
import { Label } from "@workspace/ui/components/label";
import { Loader2, Lock } from "lucide-react";
interface LoginFormData {
email: string;
password: string;
}
export default function LoginForm() {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const searchParams = useSearchParams();
const clientId = searchParams.get("client_id");
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setError("");
const formData = new FormData(e.currentTarget);
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// You can now use email and password variables
console.log("Email:", email);
console.log("Password:", password);
};
// If no session, show login form
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 p-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1 text-center">
<div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-primary/10 mb-4">
<Lock className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
<CardDescription>
{clientId ? (
<>
App <span className="font-medium">{clientId}</span> is
requesting access to your account
</>
) : (
"Sign in to your account to continue"
)}
</CardDescription>
</CardHeader>
<CardContent>
{error && (
<Alert variant="destructive" className="mb-6">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
name="email"
type="email"
placeholder="Enter your email"
required
disabled={loading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
name="password"
type="password"
placeholder="Enter your password"
required
disabled={loading}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Sign in
</Button>
</form>
<div className="mt-6 text-center space-y-2">
<p className="text-sm text-muted-foreground">
Don't have an account?{" "}
<a
href="/register"
className="font-medium text-primary hover:underline"
>
Sign up
</a>
</p>
<p className="text-xs text-muted-foreground">
<a href="/forgot-password" className="hover:underline">
Forgot your password?
</a>
</p>
</div>
</CardContent>
</Card>
</div>
);
}
I want to integrate this to login with kratos, how would I do that?chilly-king-10285
09/17/2025, 8:48 PMchilly-king-10285
09/17/2025, 8:49 PMflow
query parameter. If it doesn't exist, redirect to the Kratos endpoint to create a new login flow. If it does, fetch the flow data. js import { useEffect, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { FrontendApi, Configuration, LoginFlow } from "@ory/client"; const frontend = new FrontendApi( new Configuration({ basePath: "https://<your-project-slug>.<http://projects.oryapis.com|projects.oryapis.com>", baseOptions: { withCredentials: true }, }) ); export default function LoginForm() { const [flow, setFlow] = useState<LoginFlow>(); const router = useRouter(); const searchParams = useSearchParams(); const flowId = searchParams.get("flow"); useEffect(() => { if (!flowId) { // No flow ID, redirect to Kratos to create a new flow router.replace( `/login?flow=${/* fetch new flow id from Kratos or redirect to Kratos endpoint */}` ); return; } // Fetch the flow data frontend .getLoginFlow({ id: flowId }) .then(({ data }) => setFlow(data)) .catch(() => { // handle error, possibly redirect to create a new flow }); }, [flowId]); // ... }
[See full example in Ory docs](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow) --- ### 2. Render the Form Use the flow.ui.nodes
array to render all required fields, including hidden fields like CSRF tokens. You can map these nodes to your custom UI components. js <form action={flow.ui.action} method={flow.ui.method} onSubmit={handleSubmit} > {flow.ui.nodes.map((node, idx) => { if (node.attributes.type === "hidden") { return ( <input key={idx} type="hidden" name={node.attributes.name} value={node.attributes.value} /> ); } // Map other input types (email, password, etc.) to your custom Input component // ... })} <Button type="submit" disabled={loading}> {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} Sign in </Button> </form>
[See mapping example](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow) --- ### 3. Submit the Login Flow On form submission, send a POST request to the flow.ui.action
URL with all form data, including the CSRF token and the flow ID. js const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); setError(""); const formData = new FormData(e.currentTarget); const body = Object.fromEntries(formData); try { await frontend.updateLoginFlow({ flow: flow.id, updateLoginFlowBody: body, }); // On success, redirect to your app's home or dashboard router.replace("/"); } catch (err) { // Handle errors, e.g., show error messages setError("Login failed"); setLoading(false); } };
[See submission example](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow) --- ### 4. Reference Implementations Ory provides full reference implementations for Next.js/React you can use as a starting point: - [Ory Next.js/React Example](https://github.com/ory/kratos-nextjs-react-example) - [Ory Docs: Custom UI Quickstart](https://www.ory.sh/docs/getting-started/custom-ui) --- Summary: - Always initialize and fetch the login flow from Kratos. - Render all fields from flow.ui.nodes
, including hidden ones. - Submit the form to the URL provided in flow.ui.action
. - Use the Ory SDK (@ory/client
) for API calls. For more details and advanced flows (social login, 2FA, etc.), see the [Ory Kratos custom UI integration docs](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration) and the [Next.js quickstart](https://www.ory.sh/docs/getting-started/integrate-auth/nextjs) [Custom UI Basic Integration](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration) [Get Login Flow Example](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow) [Submit Login Flow Example](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow)wooden-addition-60810
09/17/2025, 9:08 PMchilly-king-10285
09/17/2025, 9:08 PMchilly-king-10285
09/17/2025, 9:08 PMflow
query parameter. If it is missing, you should redirect the user to the Kratos endpoint to create a new login flow. This is the recommended approach in Ory's documentation for both server-side and client-side rendered applications. Here’s how you can implement this in your Next.js page: 1. Check for the flow
parameter: On page load, check if the flow
query parameter exists. 2. If missing, redirect to Kratos to create a new flow: Redirect the browser to the Kratos login flow initialization endpoint (e.g., /self-service/login/browser
). Kratos will then redirect back to your login page with a new flow
parameter. Example (React/Next.js): js import { useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; export default function LoginForm() { const router = useRouter(); const searchParams = useSearchParams(); const flow = searchParams.get("flow"); useEffect(() => { if (!flow) { // Redirect to Kratos to initialize a new login flow window.location.href = `https://<your-kratos-url>/self-service/login/browser?return_to=${encodeURIComponent(window.location.href)}`; } }, [flow]); // ...rest of your component }
- Replace <your-kratos-url>
with your actual Kratos public endpoint. - The return_to
parameter ensures the user is redirected back to your login page after the flow is created. This approach is directly supported and recommended in the Ory documentation for custom UIs and is necessary for the login flow to work correctly with Ory Kratos [Create a flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-a-flow) [Initialize login flow](https://www.ory.sh/docs/kratos/self-service/flows/user-login#initialize-login-flow). Summary: - If flow
is missing, redirect to the Kratos login flow initialization endpoint. - Kratos will redirect back to your login page with a valid flow
parameter. This ensures your login page always has a valid flow to work with.wooden-addition-60810
09/17/2025, 9:11 PMchilly-king-10285
09/17/2025, 9:11 PMchilly-king-10285
09/17/2025, 9:11 PM<http://localhost:3000>
with credentials (cookies). Key points from Ory documentation: - Ory Network does not allow CORS origins to be set to *
, null
, or localhost/127.0.0.1
. For local development, you must use [Ory Tunnel](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel), which allows your app and Ory to share the same top-level domain and enables cookies and CORS to work correctly [Set up CORS for Ory Network](https://www.ory.sh/docs/guides/cors) [Set up custom domains](https://www.ory.sh/docs/guides/custom-domains#cors). - For production, you should add your actual frontend domain as an allowed CORS origin in the Ory Console or via the CLI. For local development, do not try to add localhost
or 127.0.0.1
as a CORS origin—this is not supported [Set up CORS for Ory Network](https://www.ory.sh/docs/guides/cors). - When using Ory Tunnel, you run both your app and Ory APIs on the same domain (e.g., localhost:4000
), which resolves CORS and cookie issues [Ory Tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel). What you should do: - For local development, use Ory Tunnel: bash ory tunnel --project <your-project-id-or-slug> <http://localhost:3000>
Then, point your frontend and API requests to <http://localhost:4000>
instead of the remote Kratos URL. This will allow cookies and CORS to work as expected during development [Local development](https://www.ory.sh/docs/getting-started/local-development) [Ory Tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel). - For production, ensure your deployed frontend domain is added as an allowed CORS origin in your Ory project settings. Summary: You cannot use localhost
as a CORS origin directly with Ory Network. Use Ory Tunnel for local development to avoid CORS and cookie issues. For production, configure your real domain as an allowed CORS origin [Set up CORS for Ory Network](https://www.ory.sh/docs/guides/cors) [Ory Tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel). If you need more details on how to set up Ory Tunnel, see the [official documentation](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel).wooden-addition-60810
09/17/2025, 9:13 PMchilly-king-10285
09/17/2025, 9:13 PMchilly-king-10285
09/17/2025, 9:14 PMlocalhost
or 127.0.0.1
consistently for both your app and Kratos. The key is to ensure that both your frontend and Kratos are accessible on the same domain (either localhost
or 127.0.0.1
, but not mixed), and that your CORS and cookie settings are configured correctly. ### Key Points for Local Development with Self-Hosted Kratos 1. Use the Same Domain Consistently Make sure both your frontend and Kratos are running on the same domain (e.g., both on localhost
or both on 127.0.0.1
). Do not mix them, as browsers treat them as different domains for cookies and CORS [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). 2. CORS Configuration In your Kratos config (kratos.yml
), enable CORS for your frontend's origin. For example, if your frontend runs on <http://localhost:3000>
, your config should look like: yaml serve: public: cors: enabled: true allowed_origins: - <http://localhost:3000> allowed_methods: - POST - GET - PUT - PATCH - DELETE allowed_headers: - Authorization - Cookie - Content-Type exposed_headers: - Content-Type - Set-Cookie
[Cross-origin resource sharing (CORS) settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors) 3. Cookie Settings For local development, you can run Kratos in --dev
mode, which relaxes some cookie security settings (like SameSite
and Secure
flags) to work over HTTP [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). bash kratos serve --dev
Do not use --dev
in production! 4. Ports Are Fine Running your frontend and Kratos on different ports is fine as long as the domain is the same [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). 5. No Need for Ory Tunnel Ory Tunnel is only required for Ory Network (cloud), not for self-hosted Kratos. ### Example Setup - Frontend: <http://localhost:3000>
- Kratos Public API: <http://localhost:4433>
Both must use localhost
(or both 127.0.0.1
), and your Kratos CORS config must allow <http://localhost:3000>
. ### Troubleshooting - If you see CORS or cookie issues, double-check that you are not mixing localhost
and 127.0.0.1
anywhere in your URLs, configs, or browser requests. - Make sure your browser is not blocking cookies due to SameSite
or Secure
flags—use --dev
mode for local development. For more details, see the official Ory docs on [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf) and [CORS settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors). --- Summary: You can and should use localhost
for local development with self-hosted Kratos. Just ensure both your app and Kratos use the same domain and configure CORS and cookies as described above.wooden-addition-60810
09/17/2025, 9:15 PMchilly-king-10285
09/17/2025, 9:15 PMchilly-king-10285
09/17/2025, 9:15 PM<http://kratos.eagle-rfid.com>
and your frontend is running on <http://localhost:3000>
, you will encounter CORS and cookie issues because browsers enforce strict same-origin policies. According to the Ory documentation, cookies and CORS work best when both your frontend and Kratos are on the same domain. Running them on different domains (such as localhost
and a custom domain) is not recommended for local development, and will cause issues with session cookies and CSRF protection. The recommended approach for local development is: - Run both your frontend and Kratos on the same domain (e.g., both on localhost
or both on your custom domain). - If you must use different domains, you will not be able to share cookies or have a seamless login experience due to browser restrictions. The Ory documentation explicitly states that cookies will not work across different domains, and CORS will not resolve the cookie/session issue [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). > Cookies work best on the same domain. While it's possible to get cookies running on subdomains it isn't possible to do that across Top Level Domains (TLDs). ... Running the apps on different domains won't work at all, such as <https://kratos-my-website/>
and <https://secureapp-my-website/>
[CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). What you can do: - For local development, either: - Run Kratos on localhost
(e.g., `http://localhost:4433`) and configure CORS and cookies for localhost
, or - Run your frontend on a subdomain of <http://eagle-rfid.com|eagle-rfid.com>
(e.g., <http://app.eagle-rfid.com|app.eagle-rfid.com>
) and configure CORS and cookies for .<http://eagle-rfid.com|eagle-rfid.com>
. If you cannot change your Kratos domain, you will not be able to use cookies for authentication with a frontend running on localhost
. This is a browser security limitation, not an Ory-specific one. Summary: You cannot develop with your frontend on localhost
and Kratos on a different domain (<http://kratos.eagle-rfid.com|kratos.eagle-rfid.com>
) and expect cookies and sessions to work. You must run both on the same domain or subdomain for local development [CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). If you need to support multiple domains (e.g., for multi-brand or multi-tenant setups), Ory Network and Ory Kratos Enterprise support this via the Multibrand feature, but this is not available in the open-source self-hosted version [Advanced base URL, CSRF and session cookie settings](https://www.ory.sh/docs/kratos/guides/multi-domain-cookies).