The future of authentication is decentralized
Authentication is a fundamental building block of all modern applications, allowing users to interact with online platforms securely.
However, the traditional process of logging into applications has remained largely unchanged for the past decade, putting the control of users' data and identities in the hands of large corporations.
But recently, web3 has presented opportunities for new and innovative authentication methods that put the power back in the hands of users and unlock new capabilities for what’s possible.
At thirdweb, we’ve embraced this opportunity by creating thirdweb Auth, the first ever fully self-custodied web3-native form of authentication.
In this post, we'll explain what thirdweb Auth is, why we created it, and how you can use it to supercharge your applications.
The story of modern authentication
Before diving into thirdweb Auth, it's important to understand the evolution of modern authentication and why web3 presents new opportunities.
During the days of web1, where we could only read data on the internet from static web pages, there was no need for authentication.
With the advent of web2, the concept of writing data to an application was introduced, and people needed a way to associate specific data with themselves.
So, the concept of identity was introduced to enable users to store unique data associated with their own accounts.
Additionally, users needed a way to prove to applications that they held a specific identity - they needed to present proof of identity.
As a result, you would create a username as your identity and a password to use as a proof-of-identity for each application. Authentication consisted of providing your username and password to each application.
But this came with certain disadvantages - every application created its own private form of identity, stored somewhere in its database, forcing users to inconveniently create a multitude of identities across each platform, while owning control to none of these identities.
However, with the introduction of web3, ownership and identity are inherently public. Users now have a single, convenient, publicly-accepted identity that could be used for authentication - their wallet address.
This presents the opportunity to allow users to login with their wallets.
Unlocking the capabilities of web3 authentication
Allowing users to login with their wallets would solve many of the UX issues of modern authentication while offering several advantages:
- Wallet-based authentication eliminates the need for users to create a new private identity for every new application.
- Logging in with a wallet would enable a simple one-size-fits-all form of login that users could use to login everywhere.
- Applications could access rich on-chain data associated with their users and interact with their users on-chain, bridging the gap between web2 and web3 experiences.
- Most importantly, users would have full control over their own identities.
Standards like Sign-in with Ethereum have begun to provide the puzzle pieces to put together such a flow, by enabling a standardized web3-native form of proof-of-identity.
However, with the developer tools currently available, there’s still no standard end-to-end flow that provides the advantages of web3-native authentication:
The Sign-in with Ethereum flow is an important piece of the puzzle, but still leaves out many of the most difficult parts of authentication, like identity management, session management, and security.
Worse, in the absence of a standard solution, people have resorted to standard web2 services like Auth0 to manage their authentication flows, turning control over to a centralized third party and negating much of the value that web3-native authentication has to offer.
This is why we decided to build thirdweb Auth, an end-to-end authentication system that lets anyone integrate fully self-custodied web3-native authentication into their apps and unlock the full power of web3.
The secret sauce of thirdweb Auth
At its core, thirdweb Auth enables a new form of authentication, called wallet-to-wallet authentication, eliminating the need for third parties.
This means that users can login to applications with their wallets, and applications can authenticate and authorize users with their wallets, with no dependency on any external service.
This is accomplished by enabling users to login with the Sign-in with Wallet standard and enabling the application to issue and verify authentication tokens to its users with a server-side authority wallet.
Let’s take a look at the specifics of this flow to gain an understanding of how wallet-to-wallet authentication actually works.
1. Users login with their wallet
As per the Sign-in with Ethereum and Sign-in with Wallet specifications, users sign a standardized message that’s used as a login request to a specific application.
An example login message might look like the following:
thirdweb.com wants you to sign in with your wallet:
0x9e1b8A86fFEE4a7175DAE4bDB1cC12d111Dcb3D6
URI: <https://thirdweb.com/login>
Version: 1
Nonce: 8e7ba884-1a9e-4ef2-a52a-9922a09a1dfe
Expiration Time: 2023-02-06T06:10:17.103Z
It specifies important information for your login requests, like the domain of the application you’re signing into (used to prevent phishing attacks), your own wallet address or ENS, and a nonce to prevent replay attacks (where your signed message is used again by an attacker to authenticate as you).
When logging in, you would be prompted to sign such a message with your wallet. Then the client would send this signed message, along with the relevant data within the message, to the server to login.
The following is an example of what such a login request payload may look like (although simplified for the sake of demonstration):
{
"message": "thirdweb.com wants you to sign in with your wallet:"
"signature": "0x9e1b8A86fFEE4a7175DAE4bDB1cC12d111Dcb3D67175DAE4bDB1cC12d1"
}
Such a payload is sent to the backend server that the user is attempting to authenticate to, where the server can verify the client-side wallet address (by checking that the signature
is in fact a signed form of the provided message
by the user that's logging in).
2. Servers issue a JWT with their wallet
The next step is where the interesting part of wallet-to-wallet authentication comes in.
At this point, the server knows the wallet address of the client-side user and can use this information to make on-chain checks or interact with the user on-chain.
However, in order for this system to work, the user would have to sign-in every time they want to make an authenticated request to the server.
In order to solve this problem, traditional authentication uses small data payloads called JSON web tokens (JWTs) to save user session data across requests.
The JWT is simply a JSON object with information about the logged-in user, like when their login expires, any relevant user data, and in our case, their wallet address.
Importantly, JWTs are signed by the server, so the server can later verify that it created and authorized each JWT.
Once the user logs in, the server can send a JWT to the user for later use. Then, when the user wants to make an authenticated request in the future, they can send the JWT along with it, which the server can use to extract their identity and relevant information.
This allows the server to securely access the identity of the user while “remembering” that the user already logged in successfully once, without forcing them to prove their identity on every request.
Auth takes advantage of standard JWTs to persist user sessions - but it has one key difference from traditional JWT flows.
In standard JWT flows, each token is signed by a random secret which is often managed by a service like Auth0. This results in an architecture where users need to talk to a third-party service to authenticate, and applications don’t really have the keys to authenticating their users.
Unlike traditional JWT flows, the JWT tokens in Auth are not signed by a random secret, managed by a third party. Instead, the JWT is signed by a wallet - which can be thought of as the server-side admin wallet.
Additionally, Auth adapts traditional JWT claims (the name for the data fields within a JWT) to store data specific to this wallet-based authentication setup.
More specifically, the wallet address of the user attempting to login and the server wallet address that issued the token are stored on the JWT in the traditional iss
and sub
claims - below is an example of an Auth JWT payload:
{
"iss": "0x9e1b8A86fFEE4a7175DAE4bDB1cC12d111Dcb3D6", // Server-side wallet
"sub": "0xa05271523BD00593eb4CC6DCbDcbd045361a9a03", // Client-side wallet
"aud": "example.com", // Domain that the payload was intended for
"iat": 1653884584, // Unix time (epoch seconds) when payload was issued
"exp": 1653884613, // Unix time (epoch seconds) when payload expires
"nbf": 1653884584, // Time before which payload is invalid
"jti": "82da3d83-9b7b-4cdd-a890-f4d9b25415be" // uuidv4
}
In this way, Auth implements a completely self-custodied wallet-to-wallet form of authentication where users login with their wallets, and applications authenticate and authorize user’s on the server-side with an admin wallet, without any intermediaries.
This unlocks a truly web3-native authentication flow.
Integrating with thirdweb Auth
Auth provides a set of developer SDKs to build this wallet-to-wallet authentication system into your web2 and web3 apps, using common existing tools like Next, Express, React, and more coming soon (like next-auth, Firebase, Supabase, etc.)
It abstracts away the complexity of the underlying process into an API that should feel familiar and ergonomic to developers used to traditional forms of authentication.
For example, adding support for Auth to an express server to let your users login with their wallets is as simple as adding the following code:
const app = express();
// Setup the auth plugin with your wallet
const { authRouter, authMiddleware, getUser } = ThirdwebAuth({
domain: "example.com",
wallet: new PrivateKeyWallet("my-private-key"),
});
// Add auth to your express server
app.use(authMiddleware);
app.use("/auth", authRouter);
But what’s actually going on underneath to enable this simple interface? After all, we need our API to handle user login and authentication, authorization, and logging out.
This is where the last bit of magic behind Auth comes in - Auth plugins automatically setup all the API endpoints you need to get going, similar to how web2 solutions like next-auth
work!
Under the hood, Auth plugins like the one in the code snippet above automatically setup the following endpoints for you:
/auth/login
to handle all the intricacies of wallet-based login, verifying login payloads, and issuing signed JWT tokens/auth/user
used for retrieving the authenticated user’s data/auth/logout
to allow users to logout with their wallets
On top of this, Auth lets you easily integrate databases and other side-effects into your login flows like you would with a standard web2 authentication flow.
Additionally, integrating this login system into your client-side apps is just as easy. For example, here’s what a simple Auth setup looks like with React, allowing users to connect and login with their wallets:
const address = useAddress();
const connect = useMetamask();
const { login } = useLogin();
const { logout } = useLogout();
const { user, isLoggedIn } = useUser();
return (
<div>
{isLoggedIn ? (
<button onClick={() => logout()}>Logout</button>
) : address ? (
<button onClick={() => login()}>Login</button>
) : (
<button onClick={() => connect()}>Connect</button>
)}
<pre>User: {JSON.stringify(user)}</pre>
</div>
);
These tools make it possible to drop a truly end-to-end wallet-based login system into your apps without worrying about the details of what goes on underneath.
With this improved DX, we aim to make it easier than ever to add web3 authentication flows into your apps, giving users control of their identities, and bridging the gap between web2 and web3 applications!
Thanks for reading! If you liked this post, you can learn more about Auth on thirdweb's Auth documentation. Also, check out my Twitter for more, and join the thirdweb Discord community.