Chromia
Start Building

06 / 11Beginner

Authentication

Your backend and frontend now work, but nothing stops one user from acting on behalf of another. The next step is adding user accounts and authentication so each user can only do what they are allowed to do.

Web2 apps usually authenticate people with a username and password, or through a third-party identity provider such as Google or Facebook. The user signs up, and a centralized server stores their credentials or authentication tokens.

Web3 takes a different approach based on cryptographic keypairs. The network identifies a user by their public key, and the user proves who they are by signing transactions with their private key. Keys are often kept in blockchain wallet apps such as Metamask or Trust Wallet, and the user fully controls their own identity on the network.

The two sections below compare how this works in a Web2 application and in a Chromia dapp.

Web2 authentication with JSON Web Tokens

This is a short refresher on the core ideas, not a full treatment of Web2 authentication. A widely used method is the JSON Web Token (JWT), and services like Auth0 or Firebase can take much of the work off your hands. In a typical flow, the user enters a username and password, the credentials go to the auth service, and the service returns a JWT that the browser stores.

With a valid JWT in hand, the frontend attaches the token to its backend API requests. Assuming Auth0, the createPost function from earlier would change roughly like this:

javascript
// Create a postfunction createPost(content) {  return auth0Client    .getIdTokenClaims()    .then((tokenClaims) => {      return axios.post(        `${API_BASE_URL}/posts`,        { content },        {          headers: {            Authorization: `Bearer ${tokenClaims.__raw}`,          },        }      );    })    .then((response) => response.data);}

The token travels in the request's Authorization header. On the backend, you verify that the token is valid:

javascript
// server.jsimport express from "express";import bodyParser from "body-parser";import jwt from "express-jwt";import jwksRsa from "jwks-rsa";import { User, Post } from "./db.js";const app = express();const PORT = 3000;app.use(bodyParser.json());// Auth0 middleware configurationconst checkJwt = jwt({  secret: jwksRsa.expressJwtSecret({    jwksUri: "YOUR_AUTH0_DOMAIN/.well-known/jwks.json",  }),  audience: "YOUR_AUTH0_AUDIENCE",  issuer: "YOUR_AUTH0_DOMAIN",  algorithms: ["RS256"],});app.use(checkJwt);// Express routes here, as in our earlier example...app.listen(PORT, () => {  console.log(`Server is running on port ${PORT}`);});

You can go further and use the user data inside the JWT to decide whether the user may perform a given action. For example, rather than having the client send a userId with each new post, the endpoint can confirm the user is authenticated and read their userId from the JWT. That way, a user can only create posts tied to their own account.

The next section shows how the same goal is reached in Web3 on Chromia.

Web3 authentication with Chromia's FT4 protocol

In Web3, each user has a cryptographic keypair, typically stored in a wallet app on a device they control. The user signs every transaction with it before sending it to the blockchain platform, which checks the signature to confirm the sender's identity.

Chromia's FT4 protocol simplifies this. The FT4 library connects to popular crypto wallets and helps you write operations through which users can register accounts.

As in the Web2 example, assume the user already has an account in your dapp. To add authentication and authorization to your Rell operations, you only need an auth handler plus a call to auth.authenticate() inside each operation:

rell
import lib.ft4.auth;@extend(auth.auth_handler)function () = auth.add_auth_handler(  flags = ["MySession"]);operation make_post(content: text) {  val account = auth.authenticate();  require(content.size() <= 255, "A post cannot be longer than 255 letters");  val user = user @ { account };  create post ( user, content );}

With the backend protected, turn to the frontend. This example assumes the user's keypair lives in the Metamask wallet app. You will use Chromia's FT4 TypeScript library to connect to Metamask and let users authenticate.

The following JavaScript lets a user connect their Metamask wallet to your dapp frontend and logs them in if their Metamask key is recognized.

typescript
async function setupAuthAndLoginUser() {  // 1. Connect with MetaMask  const evmKeyStore = await createWeb3ProviderEvmKeyStore(window.ethereum);  // 2. Get all accounts associated with evm address  const evmKeyStoreInteractor = createKeyStoreInteractor(client, evmKeyStore);  const accounts = await evmKeyStoreInteractor.getAccounts();  if (accounts.length > 0) {    // 3. Start a new session    session = await evmKeyStoreInteractor.getLoginManager().login({      accountId: accounts[0].id,      config: {        flags: ["MySession"],      },    });  }}

The setupAuthAndLoginUser function retrieves the accounts linked to the user's Metamask wallet address. If it finds an account for that address, it starts a new session for the user by calling the FT4 library's login function.

Once the user is authenticated, you can sign transactions and send them to the dapp backend. Instead of calling operations directly through the postchain-client library, call them through the FT4 session instance. The FT4 library wraps the operation in a transaction and signs it for you:

typescript
function createPost(content) {  return await session.call({    name: "make_post",    args: [content],  });}

You now have the core building blocks of a production-ready Web3 dapp on Chromia. This was only a quick overview; for a deeper look at Rell and the Chromia libraries, work through the intro to dapp development course or the advanced dapp development course.

The next part moves beyond code to broader topics: scalability, security, and the economics of Web3 dapps.

Written for this site from Chromia’s public course. Code samples are the course’s own.