Skip to main content

Integrate a Portal with your app login

note

If you haven't already done so please check out our Platform Quickstart guide to get your API integrated with Speakeasy before continuing with this guide.

Your Speakeasy powered DevEx portal will need a way to authenticate users. This guide will walk you through the steps to integrate your portal with your existing app's login. Currently Speakeasy does not provide a fully managed login solution but will look to do so in the near future).

Dev Portal Login Callback

When setting up the developer portal you will be prompted to provide a "Login Callback URL". This is the URL that Speakeasy will redirect the user to confirm login. It will also be used to provide a customer_id for each user. This allows us to power dev portal features while being aware of but not coupled to your customer tenancy model.

login callback.

Login Flow

A complete login flow may look like this:

  1. User arrives in the Speakeasy Portal and, because they are not logged in to the portal, is directed to a Login page on your site
  2. User logs in (or is already logged in)
  3. Your site creates a JWT redirect URL with the user’s information.
  4. User is redirected to the Speakeasy DevEx Portal and logged in to Speakeasy.

Once your API User is sent from Speakeasy’s Portal to your company’s site, you’ll need to redirect the users back to Speakeasy with a token created by the getPortalLoginToken method on the Speakeasy serverside SDK.

This delegates both Authentication and Authorization to your existing mechanism. You can choose to only allow some users access to the Speakeasy DevEx Portal, via introducing guarding logic around the getPortalLoginToken that will be executed by your application server.

Tenancy is expected to be provided by configuring the customer_id claim in the getPortalLoginToken API call. Each customer_id has an isolated developer portal view. This customer_id could be an email (in an email-per-tenant model), or an ID that is shared by multiple users in a tenant. Note: This customer_id corresponds to the customer_id assigned by the setCustomerId method on the Speakeasy serverside SDK when ingesting requests.

Example Callback Implementation

As an example, here is the way this could be built with a <LoginRedirect> component that would wrap the root of a react application.

import { Navigate, useSearchParams } from "react-router-dom";
import { useEffect, useState } from "react";
import { getSpeakeasyPortalLoginToken } from "api";

const LoginRedirect = () => {
useEffect(() => {
// When a callbackUrl is supplied, attempt a redirect.
if (searchParams.has("callbackUrl")) {
const callbackUrl = searchParams.get("callbackUrl")!!;

getRedirectUrl(callbackUrl).then(
(url) => {
if (url) {
window.location.replace(url);
}

searchParams.delete("callbackUrl");
setSearchParams(searchParams);
}
);
}
}, [searchParams]);

if (!redirect) {
return <></>;
}

return <Navigate to={redirect} />;
};

const getRedirectUrl = async (
callbackUrl: string,
) => {
const host = new URL(callbackUrl).host;
if (host.endsWith("your-company-name.portal.speakeasyapi.dev")) {
try {
const token = await getSpeakeasyPortalLoginToken();
const target = new URL(callbackUrl);
target.searchParams.set("speakeasyAccessToken", token.access_token);
return target;
} catch (error) {
console.warn(
"There was a problem redirecting to the callback URL.",
error
);
}
}
return "";
};

export default LoginRedirect;

If you are managing Customer API keys through Speakeasy then you will need to create a portal access token through the serverside SDK integration. More information on this here. Here is an example of doing so with the Java serverside SDK.

    @Get("/speakeasy_portal_login_token")
public String getPortalLoginAccessToken(@RequestAttribute(SpeakeasyMiddlewareController.Key) SpeakeasyMiddlewareController controller){
String customerId = "some-customer-id";

// Populate with any custom claims you want added to your created API keys
Map<String, String> jwtCustomClaims = new HashMap<>();
jwtCustomClaims.put("sub", "your-desired-sub")
jwtCustomClaims.put("user_id", "your-desired-user_id")
jwtCustomClaims.put("email", "your-desired-email")

// Populate with any permissions you want enabled/disabled for the user
Map<String, Boolean> permissions = new HashMap<>();
permissions.put("end_user:api_keys:read", true);
permissions.put("end_user:api_keys:write", true)
String accessToken = controller.getPortalLoginToken(customerId, "Some User Display Name", jwtCustomClaims,
permissions, filterBuilder.build());
// build response
}