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.

Unless you are using the Builder role, you MUST specify the App address you want to authenticate with. For more details, see the Apps section to learn how to create an App.

1

Generate an Authentication Challenge

First, generate an authentication challenge.

mutation {  challenge(    request: {      accountOwner: {        app: "<app-address>"        account: "<account-address>"        owner: "<owner-address>"      }    }  ) {    __typename    id    text  }}

If you are making this request from a non-browser environment, ensure that the HTTP Origin header is set to the correct value for your application's domain.

The challenge response will contain the id and the text of the challenge message.

Response
{  "data": {    "challenge": {      "id": "<challenge-id>",      "text": "<origin-domain> wants you to sign in with your Ethereum account…"    }  }}

The challenge text is a Sign-In with Ethereum (SIWE) message used to verify the ownership of the signer address. It is issued only if the signer address is the Account itself or an Account Manager for the given Account.

2

Sign the Challenge SIWE Message

Next, sign the challenge message with the signer's private key.

import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.APP_PRIVATE_KEY);
const signature = account.signMessage({ message });

3

Acquire Authentication Tokens

Finally, you can use the challenge.id and the signature to obtain your authentication tokens.

mutation {  authenticate(request: { id: "<challenge.id>", signature: "<signature>" }) {    ... on AuthenticationTokens {      accessToken      refreshToken      idToken    }
    ... on WrongSignerError {      reason    }
    ... on ExpiredChallengeError {      reason    }
    ... on ForbiddenError {      reason    }  }}

That's it—you are now authenticated.

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          metadata {            name            picture          }        }        permissions {          canExecuteTransactions          canTransferTokens          canTransferNative          canSetMetadataUri        }        addedAt      }      ... on AccountOwned {        account {          address          username          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 session by:

  • Refreshing your authentication tokens

  • Getting details about the current session

  • Listing all authenticated sessions

Tokens Refresh

You can refresh your authentication tokens by calling the refreshAuthentication mutation.

mutation {  refresh(request: { refreshToken: "<refresh-token>" }) {    ... on AuthenticationTokens {      accessToken      refreshToken      idToken    }
    ... on ForbiddenError {      reason    }  }}

Get Current Session

You can get details about the current session by calling the currentSession query.

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

query {  currentSession {    authenticationId    app    browser    device    os    origin    signer    createdAt    updatedAt  }}

List Authenticated Sessions

You can list all your active sessions by calling the paginated authenticatedSessions query.

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

query {  authenticatedSessions(    request: {      # app: "<app-address">, You can filter by app address      pageSize: TEN    }  ) {    items {      authenticationId      app      browser      device      os      origin      signer      createdAt      updatedAt    }    pageInfo {      prev      next    }  }}

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 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., <coming-this-week>/.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: <coming-this-week>.
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" });  }}