Aptos Keyless Auth

Osikhena OshomahOsikhena Oshomah
11 min read

Managing private keys for blockchain has always been a herculean task for non-technical folks thereby raising the barrier to entry in blockchain. Aptos makes it easier for non-technical folks to get involved with its keyless auth.

This blog post explains Aptos keyless authentication, what it is, how it works, and how developers can integrate it into their dapp. It examines the problems solved by keyless auth, analyzing the merits of using keyless auth in our dapp and considerations of developers when implementing keyless auth.

The Problem Keyless is Solving

In traditional blockchain usage, users need a wallet to interact with the blockchain and sign transactions. They must download one and keep their private key safe; otherwise, they risk losing their funds. This has raised the entry barrier to the blockchain ecosystem as many users are not tech-savvy, and writing a piece of text down might not appeal to everyone.

Figure 1: Wallet signing transactions

The blockchain industry is full of stories of users losing their funds due to the loss of their passphrase or private key, making them unable to sign transactions. That begs the question: What if there’s a secure way for users to sign transactions without needing a wallet and remembering a private key? Aptos keyless auth solves this problem. Let’s see how!

Web2 Social Login

Aptos Keyless Auth offers a blockchain login experience similar to Web2. We are familiar with web services that authenticate users using social logins.

Users click a button and are directed to an Open Identity Connect (OIDC) provider, such as Google, Facebook, or Twitter. The OIDC providers authenticate the user and redirect them back to the application, signing a token proving that the user has been authenticated.

An application developer creates the project with the OIDC provider and gets a client ID from them to identify requests from that application. The provider signs a combination of the user identity, the client ID, and a nonce, which is any arbitrary data sent from the application requesting a social login. In Figure 2, you can see an illustration of this process.

Figure 2: OIDC login process

The OIDC Provider signs and returns a token to the application, which the application can verify.

How Aptos Keyless Auth works

Aptos Keyless Auth builds on web2 authentication concepts but enhances them with key cryptographic concepts typical of web3 environments, such as zero-knowledge proofs (ZKProofs) to verify the OIDC signature.

To begin integrating Keyless Auth into a dapp, the developer must register the dapp with an OpenID Connect (OIDC) provider to obtain a client ID, which is essential for authenticating users via social login. You can see how to do that here for supported OIDC providers.

Figure 3: Overview of how keyless work

Figure 3 shows the flow for keyless auth implementation. The user generates an ephemeral key pair consisting of a private and public key. These keys have a limited lifespan and expire after a short duration to enhance security.

As I explained in the web2 social login section, the OIDC provider signs three fields: the user ID (e.g., email), the application ID, the client ID, and a nonce. In the keyless auth implementation, the nonce sent to the OIDC is a hash of the ephemeral public key, the key's expiry date, and a blinder, ensuring the provider cannot associate on-chain transactions with the user's email.

The prover service, depicted in Figure 3, leverages zero-knowledge proofs (ZKProofs) to verify the OIDC provider's signature over its public key and the user's ephemeral public key, ensuring secure authentication. The prover service is an external service verifying OIDC signatures using ZKProofs.

After successful authentication, a keyless account is created with an address derived from a hash of the user ID, application ID, and a unique 'pepper'—a 256-bit random value generated by the pepper service using a verifiable unpredictable function (VUF). This ensures the account address is domain-scoped unique to the user's email and the specific application.

Note: Only an authorized user with a valid JWT (JSON Web Token) can retrieve the pepper from the pepper service.

The primary difference between a keyless account and a standard key-based account is that keyless accounts are accessed via social login, eliminating the need for users to manage mnemonics or private keys for account security.

Integrating Aptos Keyless Auth in a Dapp

You can integrate Keyless Auth via two methods.

The first method is via the SDK, which you install in your dapp. The developer registers the dapp with the supported OIDC providers to obtain the client ID used with the user ID (e.g., the email) to generate the account address. As this method scopes the generated account address to the dapp, the generated address is only available for use on the dapp on which it was created. You can see an example of Keyless SDK auth integration on this Github repo from Aptos Labs.

The second integration method uses Aptos Connect, an online account manager tool that creates and manages an account address via social login. Users can create an Aptos blockchain account by signing in via an OIDC provider. The created account is not domain scoped to any dapps and can be used on dapps that support keyless auth.

Let’s see how to integrate Aptos keyless auth in our dapp using Aptos Connect.

For this example, we will create a React App. Use the following command to bootstrap a new React app:

pnpm create vite <your folder name>

Install the necessary packages:

pnpm i @aptos-labs/wallet-adapter-react react-modal @aptos-labs/ts-sdk

The following packages were installed:

  • @aptos-labs/wallet-adapter-react is a wallet adapter that connects supported wallets with your dapps. This package contains a React provider and context that wraps around the dapp, giving it access to wallet information and other account details. It also lets you easily specify which wallets you want to permit connections with.

  • @aptos-labs/ts-sdk is used to send transactions to the blockchain and query blockchain data, among other functionalities.

  • react-modal: React modal package.

After the installation of these packages, create a components folder inside the src directory and create two files inside the components folder, WalletProvider.tsx, and WalletSeletor.tsx

Copy the following code into the WalletProvider.tsx file:

import { PropsWithChildren } from "react";
import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react";
import { Network } from "@aptos-labs/ts-sdk";
export function WalletProvider({ children }: PropsWithChildren) {
  //wallet adapter that connects the wallet to a dapp
  return (
    <AptosWalletAdapterProvider
      autoConnect={true}
      dappConfig={{ network: Network.DEVNET}}
      onError={(error) => {
        console.log("error ", error)
      }}
    >
      {children}
    </AptosWalletAdapterProvider>
  );
}

The component is a React provider that will wrap the React app. Our dapp is set up to connect to Aptos devnet as specified in the provider configuration. The onError function runs when the connection fails, or the user cancels the connection request.

Open the main.tsx file and import the WalletProvider component to wrap the dapp.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { WalletProvider } from "./components/WalletProvider.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <WalletProvider>
       <App />
    </WalletProvider>
  </StrictMode>
);

Copy and paste the following code into WalletSelector.tsx:

import {
  APTOS_CONNECT_ACCOUNT_URL,
  AboutAptosConnect,
  AboutAptosConnectEducationScreen,
  AnyAptosWallet,
  AptosPrivacyPolicy,
  WalletItem,
  groupAndSortWallets,
  useWallet,
  isAptosConnectWallet,
} from "@aptos-labs/wallet-adapter-react";

import { useState } from "react";
import Modal from "react-modal";
Modal.setAppElement("#root");

interface WalletRowProps {
  wallet: AnyAptosWallet;
  onConnect?: () => void;
}

interface ConnectWalletDialogProps {
  close: () => void;
  isOpen: boolean;
}

export function WalletSelector() {
  const { account, connected, disconnect, wallet } = useWallet();
  const [isModalOpen, setIsModalOpen] = useState(false);
  const openModal = () => {
    setIsModalOpen(true);
  };
  const closeModal = () => {
    setIsModalOpen(false);
  };
  return (
    <div>
      {connected ? (
        <div className="p-6 bg-gray-100 rounded-md shadow-md max-w-xlg mx-auto">
          <p className="text-lg font-semibold text-green-600 mb-2">
            Wallet Connected
          </p>
          <p className="text-sm text-gray-700 mb-4">
            Account address: {account && account.address}
          </p>
          {wallet && isAptosConnectWallet(wallet) && (
            <a
              href={APTOS_CONNECT_ACCOUNT_URL}
              target="_blank"
              rel="noopener noreferrer"
              className="flex items-center justify-center text-blue-500 hover:text-blue-700 underline mb-4">View Account</a>
)}
<button onClick={disconnect} className="px-4 py-2 bg-red-500 text-white font-medium rounded hover:bg-red-600 transition"> Disconnect </button>
</div>) : (
        <div>
          <button onClick={openModal}>Connect a Wallet</button>
          <ConnectWalletDialog close={closeModal} isOpen={isModalOpen} />
        </div>
      )}
    </div>
  );
}

function AptosConnectWalletRow({ wallet, onConnect }: WalletRowProps) {
  return (
    <WalletItem wallet={wallet} onConnect={onConnect}>
      <WalletItem.ConnectButton asChild>
        <button className="w-full gap-4">
          <WalletItem.Icon className="h-5 w-5" />
          <WalletItem.Name className="text-base font-normal" />
        </button>
      </WalletItem.ConnectButton>
    </WalletItem>
  );
}

function ConnectWalletDialog({ close, isOpen }: ConnectWalletDialogProps) {
  const { wallets = [] } = useWallet();
  const { aptosConnectWallets } = groupAndSortWallets(wallets);
  const hasAptosConnectWallets = !!aptosConnectWallets.length;
  return (
    <Modal
      isOpen={isOpen}
      onRequestClose={close}
      contentLabel="Aptos Connect Example"
      style={{
        content: {
          top: "50%",
          left: "50%",
          right: "auto",
          bottom: "auto",
          marginRight: "-50%",
          transform: "translate(-50%, -50%)",
          width: "700px",
        },
      }}
    >

    <button onClick={close}>Close Modal</button>
      <AboutAptosConnect renderEducationScreen={renderEducationScreen}>
        {hasAptosConnectWallets ? (
          <>
            <span>Log in or sign up</span>
            <span>with Social + Aptos Connect</span>
          </>
         ) :
       (
          "Connect Wallet"
       )}
        {hasAptosConnectWallets && (
          <div className="flex flex-col gap-2 pt-3">
            {aptosConnectWallets.map((wallet) => (
              <AptosConnectWalletRow
                key={wallet.name}
                wallet={wallet}
                onConnect={close}
              />
            ))}
            <p className="flex gap-1 justify-center items-center text-muted-foreground text-sm">
              Learn more about{" "}
              <AboutAptosConnect.Trigger className="flex gap-1 py-3 items-center text-foreground">
                Aptos Connect
              </AboutAptosConnect.Trigger>
            </p>
            <AptosPrivacyPolicy className="flex flex-col items-center py-1">
              <p className="text-xs leading-5">
                <AptosPrivacyPolicy.Disclaimer />{" "}
                <AptosPrivacyPolicy.Link className="text-muted-foreground underline underline-offset-4" />
                <span className="text-muted-foreground">.</span>
              </p>
              <AptosPrivacyPolicy.PoweredBy className="flex gap-1.5 items-center text-xs leading-5 text-muted-foreground" />
            </AptosPrivacyPolicy>
          </div>
        )}
      </AboutAptosConnect>
    </Modal>
  );
}     
function renderEducationScreen(screen: AboutAptosConnectEducationScreen) {
  return (
    <>
      <p>About Aptos Connect</p>
      <div className="flex h-[162px] pb-3 items-end justify-center">
        <screen.Graphic />
      </div>
      <div className="flex flex-col gap-2 text-center pb-4">
        <screen.Title className="text-xl" />
        <screen.Description className="text-sm text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a]:text-foreground" />
      </div>
      <div className="grid grid-cols-3 items-center">
        <button onClick={screen.back} className="justify-self-start">
          Back
        </button>
        <div className="flex items-center gap-2 place-self-center">
          {screen.screenIndicators.map((ScreenIndicator, i) => (
            <ScreenIndicator key={i} className="py-4">
              <div className="h-0.5 w-6 transition-colors bg-muted [[data-active]>&]:bg-foreground" />
            </ScreenIndicator>
          ))}
        </div>
        <button onClick={screen.next} className="gap-2 justify-self-end">
          {screen.screenIndex === screen.totalScreens - 1 ? "Finish" : "Next"}
        </button>
      </div>
    </>
  );
}

The integration is set via the @aptos-labs/wallet-adapter-react package. Some wallet functions are imported via the package context, useWallet. The function isAptosConnectWallet checks if the connected wallet is an Aptos Connect wallet. If it is, an anchor tag directs the user to the Aptos Connect online management portal.

The package displays UI inside the modal, which educates users on Aptos Connect and how to create an account via social login. The UI elements can be styled according to our site design.

Import the WalletSelector component into App.tsx file and run the application by executing npm run dev on the command line.

import { WalletSelector } from './components/WalletSelector'
import './App.css'
function App() {
  return (
    <>
      <div className="card">
           <WalletSelector />
      </div>
    </>
  )
}
export default App

The dapp renders a button on the screen when the application is run, as shown in Figure 4. Clicking the “Connect a Wallet“ button opens a modal shown in Figure 5.

Figure 4: Connect wallet button

Figure 5: Social login modal

Click on the “Continue with Google” button to open the Google authentication page, which allows you to sign up to the dapp and create an account using your Google account, as seen in Figure 6 below.

Figure 6: account page

Figure 7: Aptos Connect modal

After authentication, you click the “Approve” button and successfully sign into the dapp using social login; you should see the success dialog from Figure 8 below. You can click the “View Account” tag and be redirected to the Aptos Connect online portal to manage all the keyless auth linked to your Gmail account.

Figure 8: Success dialog

As a dapp developer, Aptos Connect makes it easy to support keyless authentication in your application, as seen from the illustrations above. The Wallet Adapter package gives you access to ready-made UI elements to enhance the dapp.

When a Keyless Auth account submits a transaction, the Aptos Connect approval modal UI appears, functioning like a wallet. It allows the user to sign the transaction submitted to the blockchain, just as it does for any blockchain account.

The Benefits of Keyless Auth

Aptos Keyless Auth offers the following benefits:

  1. Ease of Use: Users don't have to worry about managing private keys as It simplifies the user experience, especially for newcomers to blockchain technology.

  2. Improved Security: Keyless systems reduce the risks associated with losing private keys or having them stolen, making it safer for users to interact with the blockchain.

  3. Seamless Recovery: As long as you can access the social login accounts (e.g., Gmail) used to authenticate with a dapp, you can always recover your keyless account.

  4. Better User Retention: Since users don't have to deal with the technical challenges of managing keys, they’re more likely to continue using the platform.

  5. Ease of Account Migration: A keyless account can easily be moved from device to device by signing into your blockchain account via social login.

These and many more benefits make keyless authentication a more user-friendly and secure approach to blockchain interaction.

Consideration when implementing Keyless Auth

Developers have a choice of which method of Keyless Auth integration they implement for their dapp. Both methods have their strengths and drawbacks.

Directly integrating the SDK into your dapp is more technically inclined than using Aptos Connect. A developer has to register their dapp on the supported OIDC provider, with each account created on their dapp scoped only for usage on that particular dapp which limits the account.

The developer should also present the user with the option of saving a backup of their account. This is done by rotating the authentication keys for that account and generating a private key associated with the account, allowing the user to take over the management of the private keys. So that if the dapp goes out of business, users can still use their account as a normal blockchain account.

Conclusion

Implementing keyless authentication can be a game-changer for bringing more people into the blockchain world. Aptos is at the forefront of this, aiming to make blockchain accessible to the next billion users. With keyless authentication, interacting with blockchain technology becomes easier and less intimidating.

In this post, we’ve explored what keyless authentication is and how you, as a developer, can start integrating it into your dapp. I hope this guide gave you some valuable insights.

Thanks for taking the time to read.

0
Subscribe to my newsletter

Read articles from Osikhena Oshomah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Osikhena Oshomah
Osikhena Oshomah

Javascript developer working hard to master the craft.