Sign In to NextAuth from Farcaster Frames Button presses with Farcaster AuthKit
This guide assumes you know what Farcaster Frames are, but even if you don’t you can follow the guide to implement one for authentication to your app.
There is an incredible AuthKit available that already did the work for our WebPage to setup NextAuth, Sign In With Farcaster and provides it as a sample. We’ll use this sample to get up and running and add Sign In as well as user creation via Farcaster Frames using the signed message each Frame interaction gives us.
To start simply run the following in your terminal which will set you up with a fresh example.
You can already see the sample webpage in your browser. Go sign in with Warpcast!
Make sure your .env is setup with the correct values for the following
You can get a Neynar API Key at the Neynar Webpage. The Next Auth parameters are documented in the NextAuth docs. You won’t need a NEYNAR API Key if you use another API or Hub. For this tutorial we will use Neynar.
Building the actual Frames Authentication
What we want to build is a Frame that contains a link. When the user clicks that button and is linked to our webpage we want to know that the user is real and log them in.
If we already have user data on them we can easily use that as well as we will know who is logging in. But how is it done? We are using the Farcaster Clients that will actually query our server for the link! That way we can show unique links to each user!
Well okay so why does it need to be unique and why is it secure?
We need unique links because if the link is the same how can we know what user it is? We are not able to set cookies as the user will not visit the webpage before clicking the link to the webpage. We need to have the data in the link! We can use URL parameters for that!
And security? well each Farcaster client has a Signer. That means they can sign cryptographically which demonstrates that the user is actually the user! Okay and that helps us? Yes because we can verify that signature and if it matches the user, we know it is them accessing the page and log them in.
It is like sending a password reset link to a user that will log them in to reset the password. Just that we use the authenticity and security of Farcaster Signers and don’t need to setup anything ourselves besides validation! How cool is that? We also know each user is unique since each has their own FID (userId).
So how do we get it done?
Add a Custom Credentials Provider
In NextAuth to login users we need Authentication Providers and we will use Credential Providers we create on ourselves that only takes the Farcaster Signed Message we recieve from the Farcaster client when the user clicks the button in the Frame.
To get it done we need to be able to verify that the signedMessage we get in the Magic Link is valid. That is a NextAuth Specific part and you can use the same logic we are building now for any authentication provider to login users from Farcaster Frames.
In NextAuth we have our authentification specific code in pages/api/auth/[...nextauth].ts
where all our configuration for authentification lives.
We can see that our login via Farcaster in the Quickstart worked because there already is a CredentialsProvider called “Sign in with Farcaster”. This is exactly what we did and what logged us in. Feel free to check the code as what we are building is really similar just that we will not verify SIWF messages, but the signedMessageBytes from a Farcaster Frame.
Start by creating a new CredentialsProvider and give it the id farcasterframe
. We will need this id to tell our authentication backend what provider we want to sign the user in with later. We will also give our Credentials Provider a name.
Now we have our CredentialsProvider setup, but it doesn’t know what to login with so we need to tell it that we are passing a credential of type text that is called signedMessageBytes which we name it to match what Farcaster gives us.
We are able to pass the signedmessagebytes from the Farcaster Frame to our Backend which handles authentification now.
But that is not enough. Simply passing the signed Message doesn’t mean we verified it and doesn’t log the user in. So we need to tell our Backend how to do that.
We can override the authorize
method of our CredentialsProvider and implement our custom authentification logic there.
CAREFUL You are now handling Authentication. If you screw up you will have unauthorized access, attack vectors or users not being able to log in. Check twice and talk to others to check that what you are doing really is secure and makes sense. Test the intended flow and edge cases!
To validate the signedMessage we need validate the Frame Action against a Farcaster Hub.
You could use any Hub out there to do it or you can use an API Provider like Neynar that does it for you so you don’t need to run Hubs yourself.
What is going to happen? We will send the signedmessagebytes out and get a validation response that tells us if it is valid and what the signed data is.
The signed data format can be viewed in the Farcaster Specification and contains important information like the userId (FID) of the interacting user as well as a timestamp of when the message was signed (meaning when the interaction happened). When looking at the specification of the Frame Signature Packet you will see that the untrustedData is the data format contained in the signed message.
Nice bro, but how to do auth? Simply use the Neynar API, verify the signature and return a user object if successful.
Code? Here is our authorize function that we override and takes in the credentials we defined above.
To install the Neynar SDK we run npm install @neynar/nodejs-sdk
and
add the import of the NeynarAPIClient at the top import { NeynarAPIClient } from "@neynar/nodejs-sdk";
.
Now we are ready to send the signedMessageBytes off and get the result back.
What we do here is return the same information that the Sign in with Farcaster CredentialsProvider does to stay compliant with the sample. You may and likely want to adjust this to your customer user type. It makes sense to use Farcaster ID’s as a unique mapping from FID <> User in your application. Do not use the username (fname e.g. samuellhuber) as a user can change that but will never change their FID.
Also be reminded that with the above setup one could replay the signedMessage and be signed in. We will discuss security in a dedicated section.
Now that we have a method to log users in we need to generate the authentication links, add them to our Frames and then handle the login in the Frontend.
Add a Frames Endpoint to generate Authentication Links
What is a Frames Endpoint? Simply anything that handles a POST request and returns something according to the Frames Specification. In our case we want to recieve Frame Action with it’s signedMessageBytes and return a redirect link.
Important Make sure to return 302! Do not use .redirect() in NextJS as that will override the Status Code to 307 or 308 but we need 302!
So what we will do is create a POST request handler and return our redirect link. The scaffolding of that we will create under our auth API directory and call the route createAuthLink.
pages/api/auth/createAuthLink.ts
What we need to be mindful of here is that our Frame will expect a response in any case! Meaning if our computation throws an error we absolutely need to make sure we still return a redirect! Else the user will not get a link and think our Frame is broken.
I’d much rather have the user login on the webpage manually then not have them at the webpage at all. Best of course is sign them in which reduces friction.
Okay so what do we do? You already know! We’ll use the FrameAction and create a link that has the signedMessageBytes as URL Parameters.
Why that? Doesn’t that mean they are public? Yes. Also following Kerkhoffs Principle any security system should be secure if the mechanism is public.
No issue there, we will add defence against replay attacks reusing the signedMessageBytes from the link in the security section.
the logic to handle the post will extract the signedMessage byte and any user input as well. Input is useful if you frame has input enabling you to add context to each action. You could also have the different buttons mean different pages on the webpage like Settings, Homescreen or a Profile.
That custom return could also be handled here by running a case distinction (if statements) over the button index used. For details check the Quickstart with Farcaster Frames Tutorial.
Let’s handle the error case first. In this case we’ll just link to our webpage (Home). Guaranteeing that the user will always land on our page!
Notice the explicit use of writeHead(302, ...)
to guarantee the status code of our response is a 302 redirect!
We are using the NEXTAUTH_URL as a Location since we will need that set in our environment variables anyway to make NEXTAUTH work properly!
Lets return a URL that contains our signedmessagebytes and potential user input as link to the user!
For that we will simply take what is sent and not do verification. Why? Because verification happens at the auth level anyway. No need to redo, we can’t persist that login here anyway since it’s a simple API request and we can’t set session cookies in the user’s browser.
Our handler now return a 302 redirect in all cases and has input and signedmessagebytes in the successful case, while logging errors and just redirecting to the mainpage otherwise.
If you would love the types check the types in Coinbase OnchainKit.
So now we are able to return a URL to our user!
We just need to build a frame that calls our endpoint and handle the URL parameters for signin on the application side.
Build our Frame to Link to the Webpage with SignIn
The Frame building is easy. We only need one button that pings our server so we can return the link and the user gets prompted to go there.
Read and work through the Quickstart with Farcaster Frames Tutorial if you want to learn how to build more complex Frames and learn how Farcaster Frames work in general.
In our pages/index.tsx
we are returning some HTML via the JSX and can add our Frame there, remember Frames are just HTML Metadata.
and what we want is the Farcaster Client to send a POST request to our Server so we respond with a 302 redirect and location to go to. This can be done by making a post_redirect action button.
So we set fc:frame
as metadata because that says we build a frame. Then we also set an image and a text input with placeholder channelid
because we want to get user input too.
Then we build our button. post_url will be the URL we set so ${process.env.NEXTAUTH_URL}/api/auth/createAuthLink
which I stored in a redirectUrl variable.
After specifying that we set the button text/label (content) and tell the button what it is via the action tag, namely a ‘post_redirect’.
pages/index.tsx
For the image I used a jpg that I called frame.jpg and placed in the public/
folder. You can use any image that is 1.91:1 aspect ratio or 1:1 aspect ratio as that is what will be rendered and if you do 1:1 set the aspect ratio parameter.
So our index.tsx now defines the Frame, some content we show which we can leave as is to our sample rendering the top right profile component once logged in and some text.
Handle Login on our Webpage
Now we created a Frame that we can use to get the login link with we need to handle the login on the webpage as well.
How do we do? We check for the URL parameters and then just try to log the user in.
To handle the signIn luckily we can use the provided React Hooks to SignIn.
If we just hijack the Profile component we can do it all there. To check existance of any url parameter (input and signedmessagebytes) we can use the included Router.
Why is there that weird [router]
after our function? ask react devs. Thank you. From the types it is the dependency list meaning that if anything in that list changes the effect will be executed.
Since we check for existence of our signedmessagebytes query parameter that we set in the createAuthLink API route we use to get it to the user, we know it’s present if the user wants to login.
It could also be present if someone replays the login, but we’ll discuss security here.
For now let’s actually use the sign in hook to sign a user in.
What we are now doing is using the provided signIn Hook to call our auth backend and sign a user in using our custom CredentialsProvider. This is why we set the id to farcasterframe.
Because now we can tell it to specifically use the one we want. We are passing the signedmessagebytes and like we programmed before the user is logged in if the signed data is valid.
Why redirect false? Well if we redirect the default is to redirect to the same page and then the query parameters (url/?signedmessagebytes=…) still exist so we are looping.
We can also handle that by creating custom redirect links or removing the url parameters after use.
Let’s reset the url parameters once we used them and also add some error handling.
Let’s also handle our input and make sure that there is an input variable able to be used by our frontend that we set if it’s available. Note that this input parameter may not be 100% authentic.
Now you have signed in a user from a Farcaster frame. You can try it by pinging your example with a local debugger or the warpcast tool. For more on Frames check the quickstart.
Security and other considerations for Authentication
With what we have so far on our authorize() function for the CredentialsProvider we do not check when the signature was created so anyone that at somepoint managed to get a signature for a user will be logged in. We don’t want that so we can check the timestamp present in the signed data and compare it to current time.
Checking timestamps and making sure we only allow a certain time to be passed reduced the attack surface for replay attacks.
The Input variable is used from untrusted data and from URL Parameters as is. One would need to verify the signature again and extract input from it to guarantee the input is actually the input the user signed.
This is omitted for the sake of simplicity of the concept, but might be critical in a production system.