Skip to main content

Log users in to Permit Elements

Sign a user in to Permit Elements before your application shows an element. This page is for developers who embed Permit Elements, the embeddable UI components, and need to pick and implement a login method. To create an element and get its iframe code first, see Embed Permit Elements.

Every login method ends the same way: your frontend calls permit.elements.login() from the @permitio/permit-js package, and Permit creates an element session for the user in one tenant. Most methods also need a login route in your backend that calls the Permit SDK's loginAs method.

Login methods diagram: cookie, bearer, and header methods call a login route in your server that uses the server-side SDK, while the frontend-only method calls the Permit API directly, and then the element iframe loads

Prerequisites

Choose a login method

Pick the method that matches how your frontend authenticates to your backend.

MethodloginMethod valueBackend routeUse it when
CookieLoginMethod.cookie (the default)Yes. A GET route that redirects to ticket.redirect_url.Your backend authenticates requests with a session cookie.
Bearer tokenLoginMethod.bearerYes. A POST route that returns ticket.content.Your backend authenticates requests with an Authorization: Bearer header.
Other headersLoginMethod.headerYes. A POST route that returns ticket.content.Your backend authenticates requests with a custom HTTP header.
Frontend onlyLoginMethod.frontendOnlyNo. Permit verifies the user's JWT against the environment JWKS.You don't want to add a backend route, and your identity provider issues JWTs.
Private browsingLoginMethod.supportsPrivateBrowserYes. A POST route that returns an element bearer token.Your users open the element in a browser that blocks third-party cookies, such as a private window. See Support private browsing.

The tenant you log in to is the only tenant the element shows. To show a different tenant, call permit.elements.logout() and log the user in again with the other tenant.

1. Add a backend login route

Skip this step if you use the frontendOnly method.

The backend login route identifies the signed-in user from your own authentication, then calls the server-side SDK's loginAs method with the user's key and a tenant key or ID. Both values are required. In Node.js the call takes one object:

permit.elements.loginAs({ userId, tenantId });

Each SDK follows its own naming: loginAs in Node.js, .NET, and Java, and login_as in Python, which takes the two values as positional arguments.

User must belong to the tenant

If the user is not a member of the tenant you pass to loginAs, the login fails with USER_NOT_FOUND, and the element doesn't load. See Login errors.

Initialize the Permit SDK

Create a Permit client in your backend with your environment API key. Replace <YOUR_API_KEY> with the key from Get your API key.

const { Permit } = require("permitio");
const permit = new Permit({ token: "<YOUR_API_KEY>" });

Call loginAs from the login route

Choose the tab that matches your authentication method, then your backend language. Each example is a complete server with one login route. Replace these placeholders:

PlaceholderReplace with
<YOUR_API_KEY>Your environment API key. Load it from an environment variable in production.
<USER_KEY>Code that reads the signed-in user's key from your own session or token. The key must match the user's key in Permit.
<TENANT_KEY>The key of the tenant the element shows.
  • Cookies: the route handles a GET request and redirects to ticket.redirect_url. permit.elements.login() loads the route in a hidden iframe and appends the tenant as a tenant query parameter.
  • Bearer token and Other headers: both methods use the same route. The route handles a POST request and returns ticket.content as JSON. ticket.content holds the redirect URL in a url field, which permit.elements.login() reads. The two methods differ only in how the frontend authenticates the request: an Authorization: Bearer header or your custom headers.
const express = require("express");
const { Permit } = require("permitio");

const app = express();
const permit = new Permit({ token: "<YOUR_API_KEY>" });

function getUserKey(req) {
// Replace with code that reads the signed-in user's key from your session
return "<USER_KEY>";
}

app.get("/login_cookie", async (req, res) => {
const ticket = await permit.elements.loginAs({
userId: getUserKey(req),
tenantId: "<TENANT_KEY>",
});
res.redirect(302, ticket.redirect_url);
});

app.listen(4000, () => {
console.log("Login route listening at http://localhost:4000/login_cookie");
});

2. Call permit.elements.login() in your frontend

Call permit.elements.login() after your identity provider confirms the user's identity, and before your application renders the element iframe. The element iframe reads the permit_session cookie that the login creates, so an iframe that loads first has no session.

Install permit-js

Install the @permitio/permit-js package in your frontend project:

npm install @permitio/permit-js

Set the login parameters

permit.elements.login() takes one object. The required parameters depend on the login method.

ParameterRequired forDescription
loginMethodAll methods except cookieOne of LoginMethod.cookie (default), LoginMethod.bearer, LoginMethod.header, LoginMethod.frontendOnly, or LoginMethod.supportsPrivateBrowser. Import it with import permit, { LoginMethod } from "@permitio/permit-js".
loginUrlCookie, bearer, header, private browsingThe URL of the backend login route from step 1. Ignored by frontendOnly.
tenantfrontendOnly (optional for the others)The key of the tenant to log the user in to. With backend methods, your route can set the tenant instead.
tokenBearerThe user's token. permit.elements.login() sends it as Authorization: Bearer <token> to loginUrl.
headersHeaderThe authentication headers permit.elements.login() sends to loginUrl.
userJwtfrontendOnlyThe signed-in user's JWT. Permit verifies it against the environment JWKS.
envIdfrontendOnlyThe ID of the Permit environment. The Generate Code dialog in the Elements screen shows it in the iframe src.
userKeyClaimOptional, frontendOnly onlyThe JWT claim that holds the user's Permit key, when the key is not in sub.
elementIframeUrlPrivate browsingThe exact src of the element iframe.

Log in with your method

Choose the tab for your login method. In each example, replace <TENANT_KEY> with the tenant key, and set loginUrl to the URL of your backend login route. The promise that permit.elements.login() returns resolves after the login completes, so render the element iframe in the then callback.

Set loginUrl to your cookie login route. LoginMethod.cookie is the default, so you can omit loginMethod.

import permit from "@permitio/permit-js";

permit.elements
.login({
loginUrl: "https://your_app_url.com/login_cookie",
tenant: "<TENANT_KEY>",
})
.then((loggedIn: boolean) => {
// Render the element iframe here
})
.catch((err: unknown) => {
console.error("Permit Elements login failed", err);
});

3. Log the user out of the element

Call permit.elements.logout() in the same code path that signs the user out of your application. If you skip this call, the element session stays active in that browser after the user signs out of your application, and the element still opens as that user.

permit.elements.logout();

Verify the login

  1. Open your browser's developer tools and select the Network tab.
  2. Reload the page that runs permit.elements.login().
  3. Confirm that the login request succeeds and that the response sets a cookie named permit_session.
  4. Confirm that the element iframe loads the user's data instead of an error page.

With LoginMethod.frontendOnly, step 3 is a POST to https://api.permit.io/v2/auth/<YOUR_ENV_ID>/elements_fe_login_as whose response holds a redirect_url.

If the login fails, see Login errors for the error codes Permit returns, and Troubleshoot Permit Elements for network and cookie problems.

Support private browsing

Private windows, such as Chrome Incognito, and browsers such as Safari can block the third-party permit_session cookie. With LoginMethod.supportsPrivateBrowser, your backend returns an element bearer token instead of a cookie redirect, and permit.elements.login() passes the token to the element iframe with postMessage. This method needs @permitio/permit-js version 0.5.2 or later.

Return the element bearer token from the backend

In the backend route, return ticket.element_bearer_token in a JSON field named url. permit.elements.login() reads the token from the url field of the response.

const express = require("express");
const { Permit } = require("permitio");

const app = express();
const permit = new Permit({ token: "<YOUR_API_KEY>" });

function getUserKey(req) {
// Replace with code that reads the signed-in user's key from your session
return "<USER_KEY>";
}

app.post("/login_private_browser", async (req, res) => {
const ticket = await permit.elements.loginAs({
userId: getUserKey(req),
tenantId: "<TENANT_KEY>",
});
// Return the element bearer token, not the redirect URL
res.status(200).json({ url: ticket.element_bearer_token });
});

app.listen(4000, () => {
console.log("Login route listening at http://localhost:4000/login_private_browser");
});

Pass the iframe URL to the frontend login

Set loginMethod to LoginMethod.supportsPrivateBrowser, and set elementIframeUrl to the exact src of the element iframe. If your backend route expects a bearer token or custom headers, pass token or headers as well.

import permit, { LoginMethod } from "@permitio/permit-js";

permit.elements
.login({
loginUrl: "https://your_app_url.com/login_private_browser",
tenant: "<TENANT_KEY>",
loginMethod: LoginMethod.supportsPrivateBrowser,
// If your backend route expects a bearer token, add: token: "<USER_ACCESS_TOKEN>",
// If your backend route expects custom headers, add: headers: { "<AUTH_HEADER_NAME>": "<AUTH_HEADER_VALUE>" },
// The exact src of the element iframe
elementIframeUrl:
"https://embed.permit.io/<ELEMENT_NAME>?envId=<ENV_ID>&darkMode=false&tenantKey=<TENANT_KEY>&elementsToken=true",
})
.then((loggedIn: boolean) => {
// The element iframe receives the token
})
.catch((err: unknown) => {
console.error("Permit Elements login failed", err);
});

Add elementsToken=true to the iframe URL

Append &elementsToken=true to the iframe src, and use the same URL in elementIframeUrl:

<iframe
src="https://embed.permit.io/<ELEMENT_NAME>?envId=<ENV_ID>&darkMode=false&tenantKey=<TENANT_KEY>&elementsToken=true"
width="100%"
height="100%"
frameborder="0"
></iframe>

Match the iframe URL exactly

permit.elements.login() finds the iframe whose src equals elementIframeUrl and posts the token to it. If the two URLs differ by any character, including the elementsToken=true parameter, the token never reaches the element, and the browser console logs that the iframe was not found.

Login errors

When a login fails, Permit returns one of these errors.

ErrorCauseFix
USER_NOT_FOUNDThe user doesn't exist in Permit, or the user is not a member of the tenant you logged in to.Sync the user to Permit and assign the user a role in that tenant.
TENANT_NOT_FOUNDThe tenant passed to permit.elements.login() or loginAs doesn't exist in the environment.Check the tenant key, or create the tenant.
INVALID_PERMISSION_LEVELThe user's role has no access to the part of the element being opened. A role left in Hidden Roles is the usual cause.Drag the role to a permission level. See Permission levels.
FORBIDDEN_ACCESSThe API key that the backend login route uses is not an environment API key. A project-level or organization-level key is refused here.Use the environment API key of the environment the element belongs to. See Get your API key.

A role left in Hidden Roles doesn't stop the login itself. The login creates the session for any user who is a member of the tenant, and the element then loads and asks Permit for its data. Those data requests return HTTP 403 for a hidden role, so the element renders an error instead of the user list. Assign the role a permission level to fix it.

Next steps