Authentication

This guide will help you understand how to handle authentication in Lens.


The Lens API uses authentication roles to define different levels of access and interaction:

  • Account Owner: An end-user who owns a Lens Account.

  • Account Manager: An end-user managing a Lens Account, either their own or one they have been delegated. See the Account Managers guide for more information.

  • Onboarding User: A end-user without a Lens Account, limited to features related to onboarding, such as creating an account.

  • Builder: A developer role used to authenticate and access configuration and management features.

Log In to Lens

To log in to Lens, end-user roles such as Account Owner, Account Manager, or Onboarding User need to know the EVM address of the App they want to connect to. The Builder role does not require an app address for authentication. For more details, see the Apps

A Testnet App 0xe5439696f4057aF073c0FB2dc6e5e755392922e1 is available on testnet to facilitate rapid experimentation.

We'll use viem in our example, but you can use any Ethereum wallet library that supports signing messages.

viem
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.APP_PRIVATE_KEY);

Use the PublicClient instance you created earlier to log in and acquire a SessionClient instance.

import { client } from "./client";
const authenticated = await client.login({  onboardingUser: {    app: "0xe5439696f4057aF073c0FB2dc6e5e755392922e1",    wallet: signer.address,  },  signMessage: (message) => signer.signMessage({ message }),});
if (authenticated.isErr()) {  return console.error(authenticated.error);}
// SessionClient: { ... }const sessionClient = authenticated.value;

Use the SessionClient to interact with @lens-protocol/client/actions that require authentication.

The @lens-protocol/client/actions that require authentication are explicitly labelled by the type of client they accept as first argument.

List Available Accounts

Often, when you are about to log in with an Account, you may want to list the available Accounts for the user's wallet.

You can use the paginated accountsAvailable query to list the Accounts owned or managed by a given address.

query {  accountsAvailable(    request: { managedBy: "<wallet-address>", includeOwned: true }  ) {    items {      ... on AccountManaged {        account {          address          username {            value          }          metadata {            name            picture          }        }        permissions {          canExecuteTransactions          canTransferTokens          canTransferNative          canSetMetadataUri        }        addedAt      }      ... on AccountOwned {        account {          address          username {            value          }          metadata {            name            picture          }        }        addedAt      }    }    pageInfo {      prev      next    }  }}

Continue with the Pagination guide for more information on how to handle paginated results.

See the Account Manager guide for more information on how to manage accounts.

Manage Sessions

Once you have successfully authenticated, you can manage your authenticated sessions by:

  • Keeping your session alive

  • Getting details about the current session

  • Listing all authenticated sessions

Keep Alive

Resume Session

By default, the PublicClient uses in-memory storage for the storing the authenticated session, which is lost when the current thread closes, like when refreshing a page in a browser. To keep the session persistent, supply a long-term storage solution to the PublicClient config.

In a browser, for instance, you could use the Web Storage API like window.localStorage:

client.ts
import { PublicClient, testnet } from "@lens-protocol/client";
const const client = PublicClient.create({  environment: testnet
  storage: window.localStorage,});

Then resume an authenticated SessionClient from long-term storage like so:

Resume Session
import { client } from "./client";
const resumed = await client.resumeSession();
if (resumed.isErr()) {  return console.error(resumed.error);}
// SessionClient: { ... }const sessionClient = resumed.value;

The SessionClient instance is now ready to be used for authenticated requests.

Get Current Session

Use the currentSession action:

import { currentSession } from "@lens-protocol/client/actions";

to get details about the current session.

const result = await currentSession(sessionClient);
if (result.isErr()) {  return console.error(result.error);}
// AuthenticatedSession: { authenticationId: UUID, app: EvmAddress, ... }const session = result.value;

List Authenticated Sessions

Use the fetchAuthenticatedSessions action:

import { fetchAuthenticatedSessions } from "@lens-protocol/client/actions";

to get a paginated list of all authenticated sessions.

const result = await fetchAuthenticatedSessions(sessionClient);
if (result.isErr()) {  return console.error(result.error);}
// Array<AuthenticatedSession>: [{ authenticationId: UUID, app: EvmAddress, ... }, ... ]const sessions = result.value.items;

See the Pagination guide for more information on how to handle paginated results.

Log Out

You can revoke any authenticated session by calling the revokeAuthentication mutation.

You MUST be authenticated as Account Owner or Account Manager to make this request.

mutation {  revokeAuthentication(request: { authenticationId: "<authentication-id>" })}

This will invalidate the Refresh Token for the given authenticated session. Any attempt to use them will result in forbidden error. Any issued Access Tokens will remain valid for the remainder of their short lifetime.

Get Last Logged-In Account

Coming soon


Advanced Topics

Authentication Storage

If you need to store authentication tokens in a custom storage solution with the Lens SDK, implement the IStorageProvider interface.

IStorageProvider
interface IStorageProvider {  getItem(key: string): Promise<string | null> | string | null;
  setItem(    key: string,    value: string  ): Promise<string> | Promise<void> | void | string;
  removeItem(key: string): Promise<string> | Promise<void> | void;}

For example, you can use the cookie-next library for Next.js to store and pass the authentication tokens between client and server of a Next.js application.

import { getCookie, setCookie, deleteCookie } from "cookies-next";import { IStorageProvider } from "@lens-protocol/client";
export const storage = {  getItem(key: string) {    return getCookie(key);  },
  setItem(key: string, value: string) {    await setCookie(key, value);  },
  removeItem(key: string) {    await deleteCookie(key);  },};

Authentication Tokens

Lens API uses JSON Web Tokens (JWT) as format for Token-Based authentication.

On successful authentication, Lens API issues three tokens:

  • Access Token

  • ID Token

  • Refresh Token

Lens JWTs are signed with the RS256 algorithm and can be verified using JSON Web Key Sets (JWKS) from the /.well-known/jwks.json endpoint on the corresponding Lens API environment (e.g., https://api.testnet.lens.dev/.well-known/jwks.json for testnet).

Signing keys could be rotated at any time. Make sure to cache the JWKS and update it periodically.

Access Token

Access Tokens are used to authenticate a user's identity when making requests to the Lens API.

The Access Token is required in the Authorization or x-access-token header for all authenticated requests to the Lens API.

Authorization: Bearer <access-token># orx-access-token: <access-token>

DO NOT share the Access Token with anyone. Keep it secure and confidential. If you are looking to identify a user's request on a backend service, use the ID Token instead.

Lens Access Tokens are valid for 10 minutes from the time of issuance.

Refresh Token

A Refresh Token is a credential artifact used to obtain a new authentication tokens triplet without user interaction. This allows for a shorter Access Token lifetime for security purposes without involving the user when the access token expires. You can request new authentication tokens until the refresh token is added to a denylist or expires.

DO NOT share the Refresh Token with anyone. Keep it secure and confidential, possibly on the client-side only. If you are looking to perform an operation in behalf of an Account, use the Account Manager feature instead.

Lens Refresh Tokens are valid for 7 days from the time of issuance.

ID Token

The ID Token is used to verify the user's identity on consumer's side. It contains a set of claims about the user and is signed by the Lens API.

Lens ID Tokens are valid for 10 minutes from the time of issuance, same as the Access Token.

You can use the ID Token to verify the user's identity on a backend service like described in the Consume Lens ID Tokens section.

Consume Lens ID Tokens

As briefly mentioned earlier, Lens ID Tokens can be used to verify's user identity on a backend service.

Lens ID Tokens are issued with the following claims:

ClaimDescription
subSubject - the signedBy address used to sign the Authentication Challenge. This could be the Account or an Account Manager for it. Example: 0xC47Cccc2bf4CF2635a817C01c6A6d965045b06e6.
issIssuer - the Lens API endpoint that issued the token. Typically: https://api.testnet.lens.dev.
audAudience - the Lens App address that the token is intended for. Example: 0x00004747f7a56EE7Af7237220c960a7D06232626.
iatIssued At - the timestamp when the token was issued.
expExpiration - the timestamp indicating when the token will expire. This can be used to determine if the token is still valid.
sidSession ID - the unique identifier of the session that the token was issued for.
actOptional claim that allows the token to act on behalf of another Account. This is useful for Account Managers to specify the Account address they can act on behalf of.
tag:lens.dev,2024:sponsoredCustom claim that indicates the authenticated session is enabled for sponsored transactions.
tag:lens.dev,2024:roleCustom claim that indicates the role of the authenticated session. Possible values are ACCOUNT_OWNER, ACCOUNT_MANAGER, ONBOARDING_USER, and BUILDER.

A typical use case is to use Lens issued ID Token to verify the legitimacy of user's request before issuing your app specific credentials. The following diagram illustrates this flow:

Below is an example of a Next.js middleware that demonstrates how to verify a Lens ID Token using the popular jose library:

middleware.ts
import { NextResponse } from "next/server";import { jwtVerify, createRemoteJWKSet } from "jose";
// Get JWKS URI from environment variablesconst jwksUri = process.env.NEXT_PUBLIC_JWKS_URI;const JWKS = createRemoteJWKSet(new URL(jwksUri));
export async function middleware(req) {  const token = req.headers.get("authorization")?.split(" ")[1];
  if (!token) {    return new NextResponse(      JSON.stringify({ error: "Authorization token missing" }),      {        status: 401,        headers: { "Content-Type": "application/json" },      }    );  }
  try {    // Verify the JWT using the JWKS    const { payload } = await jwtVerify(token, JWKS);
    // Optionally, attach the payload to the request    req.user = payload;
    // Proceed to the API route    return NextResponse.next();  } catch (error) {    console.error("JWT verification failed:", error);    return new NextResponse(      JSON.stringify({ error: "Invalid or expired token" }),      {        status: 401,        headers: { "Content-Type": "application/json" },      }    );  }}
export const config = {  matcher: ["/api/:path*"],};

The example works under the following assumptions:

  • The Lens ID Token is passed in the Authorization header as a Bearer token (e.g., Authorization: Bearer <ID Token>).

  • The JWKS URI is available in the NEXT_PUBLIC_JWKS_URI environment variable.

  • Your API routes are located under the /api path.

Adapt it to your specific use case as needed.

You can now use the req.user object in your API routes to access the user's identity.

Example API Route
export default function handler(req, res) {  // The JWT payload will be available as req.user if the token is valid  if (req.user) {    return res.status(200).json({ message: "Success", user: req.user });  } else {    return res.status(401).json({ error: "Unauthorized" });  }}