setInput(e.target.value)}
+ ref={inputRef}
onKeyPress={handleKeyPress}
placeholder="Type your message..."
className="flex-1"
disabled={isLoading}
/>
-
+
diff --git a/examples/nextjs-example/src/app/(app)/layout.tsx b/examples/nextjs-example/src/app/(app)/layout.tsx
new file mode 100644
index 00000000..da6fa9c1
--- /dev/null
+++ b/examples/nextjs-example/src/app/(app)/layout.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import { OpenRouterClientProvider } from "@/lib/hooks/use-openrouter-client";
+
+export default function Layout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return
{children} ;
+}
diff --git a/examples/nextjs-example/src/app/page.tsx b/examples/nextjs-example/src/app/(app)/page.tsx
similarity index 72%
rename from examples/nextjs-example/src/app/page.tsx
rename to examples/nextjs-example/src/app/(app)/page.tsx
index eb6b4de2..ba5d2db5 100644
--- a/examples/nextjs-example/src/app/page.tsx
+++ b/examples/nextjs-example/src/app/(app)/page.tsx
@@ -10,10 +10,12 @@ import {
CardTitle,
} from "@/components/ui/card";
import {
- getOAuthKeyUrl,
+ OAUTH_CALLBACK_URL,
+ OPENROUTER_CODE_VERIFIER_KEY,
OPENROUTER_KEY_LOCALSTORAGE_KEY,
- OPENROUTER_USER_ID_LOCALSTORAGE_KEY,
} from "@/lib/config";
+import { useApiKey } from "@/lib/hooks/use-api-key";
+import { useOpenRouter } from "@/lib/hooks/use-openrouter-client";
import {
ArrowRightIcon,
ExternalLink,
@@ -21,11 +23,9 @@ import {
MessageSquare,
Zap,
} from "lucide-react";
-import { useEffect, useState } from "react";
-import { OpenRouterCore } from "@openrouter/sdk/core";
-import { oAuthPostAuthKeys } from "@openrouter/sdk/funcs/oAuthPostAuthKeys";
-import { useRouter } from "next/navigation";
import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
export default function Page({ searchParams }: PageProps<"/">) {
const [connectionState, setConnectionState] = useState<
@@ -77,24 +77,45 @@ function InitializingPageContent() {
function ConnectingPageContent(props: { code: string }) {
const router = useRouter();
- const openRouter = new OpenRouterCore();
+ const { client: openRouter } = useOpenRouter();
+ const [, setApiKey] = useApiKey();
+
+ useEffect(() => {
+ const exchangeCode = async () => {
+ const codeVerifier = localStorage.getItem(OPENROUTER_CODE_VERIFIER_KEY);
- oAuthPostAuthKeys(openRouter, { code: props.code }).then((result) => {
- if (!result.ok) return;
+ if (!codeVerifier) {
+ console.error("Code verifier not found in localStorage");
+ router.push("/?error=missing_verifier");
+ return;
+ }
- if ("key" in result.value) {
- localStorage.setItem(OPENROUTER_KEY_LOCALSTORAGE_KEY, result.value.key);
- }
+ try {
+ // Exchange the authorization code for an API key using PKCE
+ const result = await openRouter.oAuth.exchangeAuthorizationCode({
+ code: props.code,
+ codeVerifier,
+ codeChallengeMethod: "S256",
+ });
- if (result.value.userId) {
- localStorage.setItem(
- OPENROUTER_USER_ID_LOCALSTORAGE_KEY,
- result.value.userId,
- );
- }
+ // Store the key and user ID
+ if (result.key) {
+ setApiKey(result.key);
+ }
+
+ // Clean up the code verifier
+ localStorage.removeItem(OPENROUTER_CODE_VERIFIER_KEY);
+
+ // Redirect to chat
+ router.push("/chat");
+ } catch (error) {
+ console.error("Failed to exchange authorization code:", error);
+ router.push("/?error=exchange_failed");
+ }
+ };
- router.push("/chat");
- });
+ exchangeCode();
+ }, [props.code, router]);
return (
@@ -152,6 +173,33 @@ function ConnectedPageContent() {
}
function DisconnectedPageContent() {
+ const [authUrl, setAuthUrl] = useState
(null);
+ const { client: openRouter } = useOpenRouter();
+
+ useEffect(() => {
+ const generateAuthUrl = async () => {
+ // Generate PKCE code challenge
+ const challenge = await openRouter.oAuth.createSHA256CodeChallenge();
+
+ // Store the code verifier for later use in the callback
+ localStorage.setItem(
+ OPENROUTER_CODE_VERIFIER_KEY,
+ challenge.codeVerifier,
+ );
+
+ // Generate authorization URL with PKCE
+ const url = await openRouter.oAuth.createAuthorizationUrl({
+ callbackUrl: OAUTH_CALLBACK_URL,
+ codeChallenge: challenge.codeChallenge,
+ codeChallengeMethod: "S256",
+ });
+
+ setAuthUrl(url);
+ };
+
+ generateAuthUrl();
+ }, []);
+
return (
@@ -171,7 +219,8 @@ function DisconnectedPageContent() {
OpenRouter Integration Demo
- This app demonstrates how to connect to OpenRouter using OAuth 2.0.
+ This app demonstrates how to connect to OpenRouter using OAuth 2.0
+ with PKCE for enhanced security.
@@ -191,7 +240,7 @@ function DisconnectedPageContent() {
Secure OAuth
- Safe authentication flow
+ Safe authentication with PKCE
@@ -206,14 +255,20 @@ function DisconnectedPageContent() {