Crafting a Node.js based framework SDK for Logto in minutes
Tags: webdev, node, opensource, identity
Published: 2024-07-02 03:06:01


Learn how to create a custom SDK for Logto using `@logto/node`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rctww1y15mwu75ritnu8.png)


Previously in this [article](https://dev.to/logto/crafting-a-web-sdk-for-logto-in-minutes-i4p), we crafted a web SDK for Logto in minutes. Now, let's focus on Node.js, another popular platform for JavaScript developers.
In this guide, we will walk you through the steps to create a simple Express SDK for Logto using `@logto/node`. This SDK will implement the sign-in flow, and you can follow the same steps to create an SDK for any other Node.js-based platform such as Koa, Next.js, NestJS, etc.

# The sign-in flow
Before we start, let's review the sign-in flow in Logto. The sign-in flow comprises of the following steps:

1. **Redirect to Logto**: The user is redirected to the Logto sign-in page.
2. **Authenticate**: The user inputs their credentials and authenticates with Logto.
3. **Redirect back to your app**: After successful authentication, the user is redirected back to your app with an auth code.
4. **Code exchange**: Your app exchanges the auth code for tokens and stores the tokens as the authentication state.

# Brief introduction of `@logto/node`

Similar to `@logto/browser`, the `@logto/node` package exposes a `LogtoClient` class that provides the core functionalities of Logto, including methods for sign-in flow:

1. `signIn()`: Generates the OIDC auth URL, and redirects to it.
2. `handleSignInCallback()`: Checks and parse the callback URL and extract the auth code, then exchange the code for tokens by calling token endpoint.
3. `getContext()`: Get the context of the current request based on the session cookie, including the authentication state and user information.

# Crafting the Express SDK
In the SDK, we will provide two route handlers (`/sign-in` and `/sign-in-callback`) along with a `withLogto` middleware:

1. `/sign-in`: A route handler that triggers the sign-in flow with a response that redirects to the OIDC auth URL.
2. `/sign-in-callback`: A route handler that processes the callback URL, exchanges the auth code for tokens, stores them, and completes the sign-in flow.
3. `withLogto` **middleware**: A middleware that calls `getContext()` to retrieve the context of the current request, including the authentication state and user information.

To use the SDK, you can simply add the middleware to your Express app to protect routes, and use the route handlers to trigger the sign-in flow and handle the callback.


## Step 1: Install the package
First, install the `@logto/node` package using npm or other package managers:

```
npm install @logto/node
```
## Step 2: Prepare the storage adapter

A storage adapter is required to initialize the `LogtoClient` instance.

Assuming that the SDK user already setup the express session, we can simply implement the `Storage` class by creating a new file `storage.ts`:

```
import type { IncomingMessage } from 'node:http';

import type { Storage, StorageKey } from '@logto/node';

export default class ExpressStorage implements Storage<StorageKey> {
  constructor(private readonly request: IncomingMessage) {}

  async setItem(key: StorageKey, value: string) {
    this.request.session[key] = value;
  }

  async getItem(key: StorageKey) {
    const value = this.request.session[key];

    if (value === undefined) {
      return null;
    }

    return String(value);
  }

  async removeItem(key: StorageKey) {
    this.request.session[key] = undefined;
  }
}
```

## Step 3: Implement the route handlers

The HTTP request is stateless, so we need to init the client instance for each request. Let's prepare a function helper to create the client instance:

```
import type { IncomingMessage } from 'node:http';

import NodeClient from '@logto/node';
import type { LogtoConfig } from '@logto/node';
import type { Request, Response } from 'express';

import ExpressStorage from './storage.js';

const createNodeClient = (request: IncomingMessage, response: Response, config: LogtoConfig) => {
  // We assume that `session` is configured in the express app
  if (!request.session) {
    throw new Error('Express session is not configured');
  }

  const storage = new ExpressStorage(request);

  return new NodeClient(config, {
    storage,
    navigate: (url) => {
      response.redirect(url);
    },
  });
};
```

In this function, we implement the `navigate` adapter along with the `ExpressStorage` adapter. The `navigate` adapter is used to redirect the user to the sign in URL.

Next, let's implement the route handlers, wrapped in a function `handleAuthRoutes`:

```
import { Router } from 'express';

const baseUrl = 'http://localhost:3000';

export const handleAuthRoutes = (config: LogtoConfig): Router => {
  const router = Router();

  router.use(`/auth/:action`, async (request, response) => {
    const { action } = request.params;
    const nodeClient = createNodeClient(request, response, config);

    switch (action) {
      case 'sign-in': {
        await nodeClient.signIn(`${baseUrl}/auth/sign-in-callback`);

        break;
      }

      case 'sign-in-callback': {
        if (request.url) {
          await nodeClient.handleSignInCallback(`${baseUrl}${request.originalUrl}`);
          response.redirect(baseUrl);
        }

        break;
      }

      default: {
        response.status(404).end();
      }
    }
  });

  return router;
};
```

1. The `/auth/sign-in` route handler triggers the `sign-in` flow by calling signIn(), a sign-in state is stored in the session, and will be consumed by the `/auth/sign-in-callback` route handler.
2. The `/auth/sign-in-callback` route handler handles the callback URL and exchanges the auth code for tokens by calling `handleSignInCallback()`, the tokens are stored in the session by the `ExpressStorage` adapter. After the exchange is done, the user is redirected back to the home page.

## Step 4: Implement the middleware

The `withLogto` middleware is used to protect routes. It calls `getContext()` to get the context of the current request, including the authentication state and user information.

```
import type { NextFunction } from 'express';

type Middleware = (request: Request, response: Response, next: NextFunction) => Promise<void>;

export const withLogto =
  (config: LogtoConfig): Middleware =>
  async (request: IncomingMessage, response: Response, next: NextFunction) => {
    const client = createNodeClient(request, response, config);
    const user = await client.getContext({
      getAccessToken: config.getAccessToken,
      resource: config.resource,
      fetchUserInfo: config.fetchUserInfo,
      getOrganizationToken: config.getOrganizationToken,
    });
    Object.defineProperty(request, 'user', {
      enumerable: true,
      get: () => user,
    });
    next();
  };
```
The function `getContext` uses the storage adapter to get the tokens from the session.

# Checkpoint: using the SDK

Now that you have crafted the Express SDK for Logto, you can use it in your app by adding the middleware to protect routes and using the route handlers to trigger the sign-in flow and handle the callback.

Here is a simple example of how to use the SDK in your Express app:

```
import http from 'node:http';

import { handleAuthRoutes, withLogto } from 'path-to-express-sdk';
import cookieParser from 'cookie-parser';
import type { Request, Response, NextFunction } from 'express';
import express from 'express';
import session from 'express-session';

const config = {
  appId: '<app-id>',
  appSecret: '<app-id>',
  endpoint: '<logto-endpoint>',
};

const app = express();
app.use(cookieParser());
app.use(
  session({
    secret: 'keyboard cat',
    cookie: { maxAge: 14 * 24 * 60 * 60 * 1000 },
    resave: false,
    saveUninitialized: false,
  })
);
app.use(handleAuthRoutes(config));

app.get('/', withLogto(config), (request, response) => {
  if (request.user.isAuthenticated) {
    return response.end(`Hello, ${request.user?.claims?.name}`);
  }

  response.redirect('/auth/sign-in');
});

const server = http.createServer(app);
server.listen(3000, () => {
  console.log('Sample app listening on http://localhost:3000');
});
```
In this example, we use the `withLogto` middleware to check the authentication state, and redirect the user to the sign-in page if they are not authenticated, otherwise, we display a welcome message.

You can check the official Express sample project [here](https://github.com/logto-io/js/tree/master/packages/express-sample).

# Conclusion
In this guide, we have walked you through the steps to create a Express SDK for Logto implementing the basic auth flow. The SDK provided here is a basic example. You can extend it by adding more methods and functionalities to meet your app's needs.

You can follow the same steps to create an SDK for any other JavaScript-based platform that runs in Node.js.

Resources:

1. [Logto Node SDK](https://github.com/logto-io/js/tree/master/packages/node)
2. [Logto Express SDK](https://github.com/logto-io/js/tree/master/packages/express)


{% cta https://logto.io/?ref=dev %} Try Logto Cloud for free {% endcta %}