mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
Seperate SIWN and SIWF
This commit is contained in:
parent
6a7d1424e9
commit
96bb45ede0
118
src/auth.ts
118
src/auth.ts
@ -6,8 +6,15 @@ declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
fid: number;
|
||||
provider?: string;
|
||||
username?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
provider?: string;
|
||||
username?: string;
|
||||
}
|
||||
}
|
||||
|
||||
function getDomainFromUrl(urlString: string | undefined): string {
|
||||
@ -29,6 +36,7 @@ export const authOptions: AuthOptions = {
|
||||
// Configure one or more authentication providers
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
id: 'farcaster',
|
||||
name: 'Sign in with Farcaster',
|
||||
credentials: {
|
||||
message: {
|
||||
@ -62,9 +70,7 @@ export const authOptions: AuthOptions = {
|
||||
},
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
const csrfToken = req?.body?.csrfToken;
|
||||
|
||||
const nonce = credentials?.nonce || csrfToken;
|
||||
const nonce = req?.body?.csrfToken;
|
||||
|
||||
if (!nonce) {
|
||||
console.error('No nonce or CSRF token provided');
|
||||
@ -91,17 +97,123 @@ export const authOptions: AuthOptions = {
|
||||
|
||||
return {
|
||||
id: fid.toString(),
|
||||
name: credentials?.name || `User ${fid}`,
|
||||
image: credentials?.pfp || null,
|
||||
provider: 'farcaster',
|
||||
};
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
id: 'neynar',
|
||||
name: 'Sign in with Neynar',
|
||||
credentials: {
|
||||
message: {
|
||||
label: 'Message',
|
||||
type: 'text',
|
||||
placeholder: '0x0',
|
||||
},
|
||||
signature: {
|
||||
label: 'Signature',
|
||||
type: 'text',
|
||||
placeholder: '0x0',
|
||||
},
|
||||
nonce: {
|
||||
label: 'Nonce',
|
||||
type: 'text',
|
||||
placeholder: 'Custom nonce (optional)',
|
||||
},
|
||||
fid: {
|
||||
label: 'FID',
|
||||
type: 'text',
|
||||
placeholder: '0',
|
||||
},
|
||||
username: {
|
||||
label: 'Username',
|
||||
type: 'text',
|
||||
placeholder: 'username',
|
||||
},
|
||||
displayName: {
|
||||
label: 'Display Name',
|
||||
type: 'text',
|
||||
placeholder: 'Display Name',
|
||||
},
|
||||
pfpUrl: {
|
||||
label: 'Profile Picture URL',
|
||||
type: 'text',
|
||||
placeholder: 'https://...',
|
||||
},
|
||||
},
|
||||
async authorize(credentials) {
|
||||
const nonce = credentials?.nonce;
|
||||
|
||||
if (!nonce) {
|
||||
console.error('No nonce or CSRF token provided for Neynar auth');
|
||||
return null;
|
||||
}
|
||||
|
||||
// For Neynar, we can use a different validation approach
|
||||
// This could involve validating against Neynar's API or using their SDK
|
||||
try {
|
||||
// Validate the signature using Farcaster's auth client (same as Farcaster provider)
|
||||
const appClient = createAppClient({
|
||||
ethereum: viemConnector(),
|
||||
});
|
||||
|
||||
const domain = getDomainFromUrl(process.env.NEXTAUTH_URL);
|
||||
|
||||
const verifyResponse = await appClient.verifySignInMessage({
|
||||
message: credentials?.message as string,
|
||||
signature: credentials?.signature as `0x${string}`,
|
||||
domain,
|
||||
nonce,
|
||||
});
|
||||
|
||||
const { success, fid } = verifyResponse;
|
||||
|
||||
if (!success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate that the provided FID matches the verified FID
|
||||
if (credentials?.fid && parseInt(credentials.fid) !== fid) {
|
||||
console.error('FID mismatch in Neynar auth');
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: fid.toString(),
|
||||
name:
|
||||
credentials?.displayName ||
|
||||
credentials?.username ||
|
||||
`User ${fid}`,
|
||||
image: credentials?.pfpUrl || null,
|
||||
provider: 'neynar',
|
||||
username: credentials?.username || undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in Neynar auth:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
session: async ({ session, token }) => {
|
||||
if (session?.user) {
|
||||
session.user.fid = parseInt(token.sub ?? '');
|
||||
// Add provider information to session
|
||||
session.user.provider = token.provider as string;
|
||||
session.user.username = token.username as string;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
jwt: async ({ token, user }) => {
|
||||
if (user) {
|
||||
token.provider = user.provider;
|
||||
token.username = user.username;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
},
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
|
||||
@ -13,9 +13,18 @@ import { useMiniApp } from '@neynar/react';
|
||||
import {
|
||||
signIn as backendSignIn,
|
||||
signOut as backendSignOut,
|
||||
useSession,
|
||||
} from 'next-auth/react';
|
||||
import sdk, { SignIn as SignInCore } from '@farcaster/frame-sdk';
|
||||
|
||||
type User = {
|
||||
fid: number;
|
||||
username: string;
|
||||
display_name: string;
|
||||
pfp_url: string;
|
||||
// Add other user properties as needed
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'neynar_authenticated_user';
|
||||
const FARCASTER_FID = 9152;
|
||||
|
||||
@ -90,6 +99,7 @@ export function NeynarAuthButton() {
|
||||
const [storedAuth, setStoredAuth] = useState<StoredAuthState | null>(null);
|
||||
const [signersLoading, setSignersLoading] = useState(false);
|
||||
const { context } = useMiniApp();
|
||||
const { data: session } = useSession();
|
||||
// New state for unified dialog flow
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const [dialogStep, setDialogStep] = useState<'signin' | 'access' | 'loading'>(
|
||||
@ -468,7 +478,7 @@ export function NeynarAuthButton() {
|
||||
nonce: nonce,
|
||||
};
|
||||
|
||||
const nextAuthResult = await backendSignIn('credentials', signInData);
|
||||
const nextAuthResult = await backendSignIn('neynar', signInData);
|
||||
if (nextAuthResult?.ok) {
|
||||
setMessage(result.message);
|
||||
setSignature(result.signature);
|
||||
@ -503,7 +513,10 @@ export function NeynarAuthButton() {
|
||||
setSignersLoading(true);
|
||||
|
||||
if (useBackendFlow) {
|
||||
await backendSignOut({ redirect: false });
|
||||
// Only sign out from NextAuth if the current session is from Neynar provider
|
||||
if (session?.user?.provider === 'neynar') {
|
||||
await backendSignOut({ redirect: false });
|
||||
}
|
||||
} else {
|
||||
frontendSignOut();
|
||||
}
|
||||
@ -528,7 +541,7 @@ export function NeynarAuthButton() {
|
||||
} finally {
|
||||
setSignersLoading(false);
|
||||
}
|
||||
}, [useBackendFlow, frontendSignOut, pollingInterval]);
|
||||
}, [useBackendFlow, frontendSignOut, pollingInterval, session]);
|
||||
|
||||
// The key fix: match the original library's authentication logic exactly
|
||||
const authenticated =
|
||||
|
||||
@ -77,7 +77,7 @@ export function SignIn() {
|
||||
const nonce = await getNonce();
|
||||
const result = await sdk.actions.signIn({ nonce });
|
||||
setSignInResult(result);
|
||||
await signIn('credentials', {
|
||||
await signIn('farcaster', {
|
||||
message: result.message,
|
||||
signature: result.signature,
|
||||
redirect: false,
|
||||
@ -96,41 +96,46 @@ export function SignIn() {
|
||||
/**
|
||||
* Handles the sign-out process.
|
||||
*
|
||||
* This function clears the NextAuth session and resets the local
|
||||
* sign-in result state to complete the sign-out flow.
|
||||
* This function clears the NextAuth session only if the current session
|
||||
* is using the Farcaster provider, and resets the local sign-in result state.
|
||||
*
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
const handleSignOut = useCallback(async () => {
|
||||
try {
|
||||
setAuthState((prev) => ({ ...prev, signingOut: true }));
|
||||
await signOut({ redirect: false });
|
||||
// Only sign out if the current session is from Farcaster provider
|
||||
if (session?.user?.provider === 'farcaster') {
|
||||
await signOut({ redirect: false });
|
||||
}
|
||||
setSignInResult(undefined);
|
||||
} finally {
|
||||
setAuthState((prev) => ({ ...prev, signingOut: false }));
|
||||
}
|
||||
}, []);
|
||||
}, [session]);
|
||||
|
||||
// --- Render ---
|
||||
return (
|
||||
<>
|
||||
{/* Authentication Buttons */}
|
||||
{status !== 'authenticated' && (
|
||||
{(status !== 'authenticated' ||
|
||||
session?.user?.provider !== 'farcaster') && (
|
||||
<Button onClick={handleSignIn} disabled={authState.signingIn}>
|
||||
Sign In with Farcaster
|
||||
</Button>
|
||||
)}
|
||||
{status === 'authenticated' && (
|
||||
<Button onClick={handleSignOut} disabled={authState.signingOut}>
|
||||
Sign out
|
||||
</Button>
|
||||
)}
|
||||
{status === 'authenticated' &&
|
||||
session?.user?.provider === 'farcaster' && (
|
||||
<Button onClick={handleSignOut} disabled={authState.signingOut}>
|
||||
Sign out
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Session Information */}
|
||||
{session && (
|
||||
<div className='my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100'>
|
||||
<div className='font-semibold text-gray-500 mb-1'>Session</div>
|
||||
<div className='whitespace-pre'>
|
||||
<div className="my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100">
|
||||
<div className="font-semibold text-gray-500 mb-1">Session</div>
|
||||
<div className="whitespace-pre">
|
||||
{JSON.stringify(session, null, 2)}
|
||||
</div>
|
||||
</div>
|
||||
@ -138,17 +143,17 @@ export function SignIn() {
|
||||
|
||||
{/* Error Display */}
|
||||
{signInFailure && !authState.signingIn && (
|
||||
<div className='my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100'>
|
||||
<div className='font-semibold text-gray-500 mb-1'>SIWF Result</div>
|
||||
<div className='whitespace-pre'>{signInFailure}</div>
|
||||
<div className="my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100">
|
||||
<div className="font-semibold text-gray-500 mb-1">SIWF Result</div>
|
||||
<div className="whitespace-pre">{signInFailure}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success Result Display */}
|
||||
{signInResult && !authState.signingIn && (
|
||||
<div className='my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100'>
|
||||
<div className='font-semibold text-gray-500 mb-1'>SIWF Result</div>
|
||||
<div className='whitespace-pre'>
|
||||
<div className="my-2 p-2 text-xs overflow-x-scroll rounded-lg font-mono border border-secondary-100">
|
||||
<div className="font-semibold text-gray-500 mb-1">SIWF Result</div>
|
||||
<div className="whitespace-pre">
|
||||
{JSON.stringify(signInResult, null, 2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user