# Protect a DaloyJS API with LoginRadius

[LoginRadius](https://www.loginradius.com) is a customer identity platform for hosted login, social login, registration, user profiles, and account-management flows. Its official [`loginradius-sdk`](https://github.com/LoginRadius/node-js-sdk) wraps the LoginRadius V2 APIs for Node.js. In a DaloyJS API, use it as a server-side verifier for LoginRadius access tokens and as a profile API client.

**Diagram: LoginRadius access-token validation**

Participants: Client app, LoginRadius, DaloyJS API

1. **Client app -> LoginRadius** (async) - Hosted login, social login, or registration flow - LoginRadius issues an access token
2. **Client app -> DaloyJS API** (request) - Authorization: Bearer <access token>
3. **DaloyJS API -> LoginRadius** (async) - authValidateAccessToken(accessToken) - server-side SDK call
4. **DaloyJS API -> LoginRadius** (async) - getProfileByAccessToken(accessToken) - optional profile lookup
5. **DaloyJS API -> Client app** (response) - 401 on invalid token, protected data on success

LoginRadius access tokens are validated through the provider API via the Node SDK. This is different from the JWT/JWKS provider pages, where verification is local after the signing keys are cached.

## 1. Configure LoginRadius

1. In the LoginRadius Admin Console, copy your **API Key**, **API Secret**, and **Site Name**.
2. Configure your hosted login, social login, registration, and callback URLs in LoginRadius. The frontend owns the login flow and sends the resulting access token to your DaloyJS API.
3. Keep the API secret on the server only. Never expose it in browser, mobile, or generated client code.

## 2. Install

```ts
pnpm add loginradius-sdk
```

## 3. Environment variables

```ts
# .env
LOGINRADIUS_API_DOMAIN=api.loginradius.com
LOGINRADIUS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LOGINRADIUS_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LOGINRADIUS_SITE_NAME=acme
LOGINRADIUS_API_REQUEST_SIGNING=false
```

## 4. Plugin

The SDK is CommonJS and does not ship first-class TypeScript types, so wrap only the methods your app needs behind a small typed boundary.

```ts
// src/plugins/loginradius.ts
import { createRequire } from "node:module";
import type { App } from "@daloyjs/core";

const require = createRequire(import.meta.url);

type LoginRadiusProfile = {
  Uid?: string;
  ID?: string;
  Email?: Array<{ Value?: string; Type?: string }>;
  Roles?: string[];
  [claim: string]: unknown;
};

type LoginRadiusAccessTokenInfo = {
  access_token?: string;
  expires_in?: number;
  [claim: string]: unknown;
};

type LoginRadiusClient = {
  authenticationApi: {
    authValidateAccessToken(token: string): Promise<LoginRadiusAccessTokenInfo>;
    getProfileByAccessToken(
      token: string,
      emailTemplate?: string | null,
      fields?: string | null,
      verificationUrl?: string | null,
      welcomeEmailTemplate?: string | null,
    ): Promise<LoginRadiusProfile>;
  };
};

const createLoginRadiusClient = require("loginradius-sdk") as (
  config: Record<string, unknown>,
) => LoginRadiusClient;

const loginRadius = createLoginRadiusClient({
  apiDomain: process.env.LOGINRADIUS_API_DOMAIN ?? "api.loginradius.com",
  apiKey: process.env.LOGINRADIUS_API_KEY!,
  apiSecret: process.env.LOGINRADIUS_API_SECRET!,
  siteName: process.env.LOGINRADIUS_SITE_NAME!,
  apiRequestSigning: process.env.LOGINRADIUS_API_REQUEST_SIGNING === "true",
});

export interface Principal {
  sub: string;
  email?: string;
  roles: string[];
  tokenInfo: LoginRadiusAccessTokenInfo;
  profile: LoginRadiusProfile;
}

export const loginRadiusPlugin = {
  name: "loginradius",
  register(app: App) {
    app.decorate("verifier", {
      async verify(token: string): Promise<Principal> {
        const tokenInfo =
          await loginRadius.authenticationApi.authValidateAccessToken(token);
        const profile =
          await loginRadius.authenticationApi.getProfileByAccessToken(token);
        const primaryEmail = profile.Email?.find((email) => email.Value)?.Value;
        return {
          sub: String(profile.Uid ?? profile.ID ?? tokenInfo.access_token),
          email: primaryEmail,
          roles: Array.isArray(profile.Roles) ? profile.Roles : [],
          tokenInfo,
          profile,
        };
      },
    });
  },
};

declare module "@daloyjs/core" {
  interface AppState {
    verifier: { verify(token: string): Promise<Principal> };
    principal?: Principal;
  }
}
```

## 5. Guard a route

```ts
import { z } from "zod";
import { App, secureHeaders, rateLimit } from "@daloyjs/core";
import { loginRadiusPlugin } from "./plugins/loginradius.ts";
import { requireAuth } from "./plugins/auth.ts"; // from the Overview page

const app = new App();
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
app.register(loginRadiusPlugin);

app.get(
  "/me",
  {
    hooks: requireAuth(),
    responses: {
      200: {
        description: "OK",
        body: z.object({
          userId: z.string(),
          email: z.string().optional(),
        }),
      },
    },
  },
  ({ state }) => ({
    status: 200,
    body: {
      userId: state.principal!.sub,
      email: state.principal!.email,
    },
  }),
);
```

## Role checks

LoginRadius profile shape depends on your account configuration and selected fields. If your site stores roles or authorization flags in the profile, normalize them in the plugin and enforce them with a narrow hooks:

```ts
import { ForbiddenError, type Hooks } from "@daloyjs/core";

export function requireLoginRadiusRole(role: string): Hooks {
  return {
    preBody: (ctx) => {
      if (!ctx.state.principal?.roles.includes(role)) {
        throw new ForbiddenError(`Requires ${role}`);
      }
      // continue
    },
  };
}
```

## Registration and account APIs

The SDK also wraps registration, password reset, email verification, access-token invalidation, account lookup, and custom-object APIs. Keep those operations in server-side routes, validate every input with your schema library, and return DaloyJS problem+json errors instead of raw SDK error objects.

## Runtimes

`loginradius-sdk` is a Node-style CommonJS SDK. Use it on the [Node adapter](/docs/adapters/node), Bun when your deployment supports CommonJS packages, Vercel Node functions, and AWS Lambda. It is not a fit for Cloudflare Workers. For edge APIs, put LoginRadius validation behind a small Node service or use direct HTTP calls from a runtime that can safely keep server secrets.

## Security notes

- Treat the LoginRadius API secret like a signing key. Store it in your platform secret manager and never send it to clients.
- Validate the access token on every protected API request, or cache positive validation results only for a short period bounded by token expiry.
- Use `rateLimit()` on login, registration, password reset, and token-validation routes. Identity endpoints are high-value abuse targets.
- Do not trust user profile fields as authorization policy until your backend has normalized them into explicit roles, scopes, or tenant memberships.

See also the [auth integrations overview](/docs/auth), [Auth0](/docs/auth/auth0), and [Clerk](/docs/auth/clerk).

---

Source: https://daloyjs.dev/docs/auth/loginradius