# How to Create Custom Connectors for Social Login in Logto: A Complete Guide

> Learn how to create custom connectors for social login in Logto. This complete guide walks you through implementing the SocialConnector interface and integrating with Logto seamlessly.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-05

---

**To create a custom social connector in Logto, implement the `SocialConnector` interface from `@logto/connector-kit` by defining metadata, a Zod configuration schema, and four core functions (`getAuthorizationUri`, `getUserInfo`, `getTokenResponseAndUserInfo`, and optionally `getAccessTokenByRefreshToken`) in a new package under `packages/connectors/`.**

Logto treats every social login integration as a *connector* — a self-contained package that bridges your application with external identity providers. According to the `logto-io/logto` source code, each connector lives under `packages/connectors/connector-<name>` and implements a standardized interface that the Logto Console discovers automatically. This architecture allows you to add support for any OAuth 2.0 or OpenID Connect provider without modifying Logto's core.

## Understanding the SocialConnector Interface

The `SocialConnector` interface exported by `@logto/connector-kit` requires three structural components and four functional implementations.

### Required Metadata and Configuration

Every connector must export a `defaultMetadata` object containing a unique `id`, display `name`, and logo paths. This metadata populates the Logto Console UI.

You must also define a **configuration guard** using Zod (`z.object({ ... })`) that validates admin-provided settings such as `clientId`, `clientSecret`, authorization endpoints, and scopes. The guard in [`packages/connectors/connector-oidc/types.ts`](https://github.com/logto-io/logto/blob/main/packages/connectors/connector-oidc/types.ts) demonstrates this pattern for OIDC providers.

### Core Functions You Must Implement

Logto invokes these functions during the authentication flow:

- **`getAuthorizationUri`** – Constructs the identity provider's authorization URL and stores a nonce in the session using `setSession`.
- **`getUserInfo`** – Exchanges the authorization code for tokens, validates the ID token or calls a user-info endpoint, and returns a normalized profile.
- **`getTokenResponseAndUserInfo`** – Returns both the raw token response and the user profile (required when storing tokens for downstream API access).
- **`getAccessTokenByRefreshToken`** (optional) – Refreshes access tokens when Logto needs to call the IdP's APIs later.

The `connector-kit` provides helpers like `constructAuthorizationUri` for OAuth 2.0 URL building and `jwtVerify` for ID-token validation.

## Step-by-Step Implementation Guide

### 1. Scaffold the Connector Package

Create a new directory at `packages/connectors/connector-my-social`. Add a [`package.json`](https://github.com/logto-io/logto/blob/main/package.json) that inherits the shared template by running `pnpm sync-preset` from the templates folder ([`packages/connectors/templates/sync-preset.js`](https://github.com/logto-io/logto/blob/main/packages/connectors/templates/sync-preset.js)).

This synchronization ensures consistent build scripts, metadata handling, and logo management across all connectors.

### 2. Define Metadata and Configuration Schema

Create [`src/constant.ts`](https://github.com/logto-io/logto/blob/main/src/constant.ts) to export static metadata:

```typescript
export const defaultMetadata = {
  id: 'my-social',
  name: 'My Social',
  logo: './logo.svg',
  logoDark: './logo-dark.svg',
};

```

Define your configuration schema in [`src/types.ts`](https://github.com/logto-io/logto/blob/main/src/types.ts) using Zod:

```typescript
import { z } from 'zod';

export const mySocialConnectorConfigGuard = z.object({
  clientId: z.string(),
  clientSecret: z.string(),
  authorizationEndpoint: z.string().url(),
  tokenEndpoint: z.string().url(),
  userInfoEndpoint: z.string().url(),
  scope: z.string().default('openid profile email'),
  customConfig: z.record(z.string()).optional(),
});

export type MySocialConnectorConfig = z.infer<typeof mySocialConnectorConfigGuard>;

```

### 3. Implement Authorization and User Info Flows

In [`src/index.ts`](https://github.com/logto-io/logto/blob/main/src/index.ts), implement the required functions. The [`connector-oidc/src/index.ts`](https://github.com/logto-io/logto/blob/main/connector-oidc/src/index.ts) file serves as the canonical reference for handling nonce generation and ID-token validation.

Your implementation must:

1. Generate a nonce using `generateStandardId()` and store it via `setSession` in `getAuthorizationUri`.
2. Retrieve the nonce via `getSession` in `getUserInfo` to prevent CSRF attacks.
3. Normalize the IdP's user profile to Logto's standard fields: `id`, `name`, `avatar`, `email`, and `phone`.

### 4. Export and Register the Connector

Export a factory function that assembles the connector object:

```typescript
const createMySocialConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => ({
  metadata: defaultMetadata,
  type: ConnectorType.Social,
  configGuard: mySocialConnectorConfigGuard,
  getAuthorizationUri: getAuthorizationUri(getConfig),
  getUserInfo: getUserInfo(getConfig),
  getTokenResponseAndUserInfo: getTokenResponseAndUserInfo(getConfig),
  getAccessTokenByRefreshToken: getAccessTokenByRefreshToken(getConfig),
});

export default createMySocialConnector;

```

Run `pnpm prepack` to synchronize presets and build the package. The connector automatically appears in the Logto Console under **Sign-in experience → Social sign-in**.

## Complete Code Example

Below is a production-ready skeleton mirroring the OIDC connector implementation in [`packages/connectors/connector-oidc/src/index.ts`](https://github.com/logto-io/logto/blob/main/packages/connectors/connector-oidc/src/index.ts):

```typescript
// packages/connectors/connector-my-social/src/index.ts
import {
  ConnectorError,
  ConnectorErrorCodes,
  ConnectorType,
  validateConfig,
} from '@logto/connector-kit';
import { constructAuthorizationUri } from '@logto/connector-oauth';
import { generateStandardId } from '@logto/shared/universal';
import { defaultMetadata } from './constant.js';
import { mySocialConnectorConfigGuard, type MySocialConnectorConfig } from './types.js';
import type {
  GetAuthorizationUri,
  GetUserInfo,
  SocialConnector,
  CreateConnector,
  GetConnectorConfig,
  GetTokenResponseAndUserInfo,
  GetAccessTokenByRefreshToken,
} from '@logto/connector-kit';

const generateNonce = () => generateStandardId();

const getAuthorizationUri: (g: GetConnectorConfig) => GetAuthorizationUri =
  (getConfig) => async ({ state, redirectUri, scope: customScope }, setSession) => {
    const cfg = await getConfig(defaultMetadata.id);
    validateConfig(cfg, mySocialConnectorConfigGuard);
    const { clientId, authorizationEndpoint, scope, customConfig } = cfg;

    const nonce = generateNonce();
    await setSession?.({ nonce, redirectUri });

    return constructAuthorizationUri(authorizationEndpoint, {
      responseType: 'code',
      clientId,
      scope: customScope ?? scope,
      redirectUri,
      state,
      nonce,
      ...customConfig,
    });
  };

const getUserInfo: (g: GetConnectorConfig) => GetUserInfo =
  (getConfig) => async (data, getSession) => {
    const cfg = await getConfig(defaultMetadata.id);
    validateConfig(cfg, mySocialConnectorConfigGuard);
    
    const { nonce, redirectUri } = await getSession?.() ?? {};
    // Implement getIdToken helper to exchange code for tokens
    const token = await getIdToken(cfg, data, redirectUri);
    const profile = await getUserInfoFromEndpoint(cfg.userInfoEndpoint, token.access_token);

    return {
      id: profile.sub,
      name: profile.name,
      avatar: profile.picture,
      email: profile.email,
      phone: profile.phone_number,
      rawData: profile,
    };
  };

const getTokenResponseAndUserInfo: (g: GetConnectorConfig) => GetTokenResponseAndUserInfo =
  (getConfig) => async (data, getSession) => {
    const token = await getIdToken(await getConfig(defaultMetadata.id), data, (await getSession?.())?.redirectUri);
    const userInfo = await getUserInfo(getConfig)(data, getSession);
    return { tokenResponse: token, userInfo };
  };

const getAccessTokenByRefreshToken: (g: GetConnectorConfig) => GetAccessTokenByRefreshToken =
  (getConfig) => async (refreshToken) => {
    const cfg = await getConfig(defaultMetadata.id);
    validateConfig(cfg, mySocialConnectorConfigGuard);
    
    const response = await fetch(cfg.tokenEndpoint, {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        client_id: cfg.clientId,
        client_secret: cfg.clientSecret,
        refresh_token: refreshToken,
      }),
    });
    
    if (!response.ok) {
      throw new ConnectorError(ConnectorErrorCodes.General, `Refresh failed: ${await response.text()}`);
    }
    return response.json();
  };

const createMySocialConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => ({
  metadata: defaultMetadata,
  type: ConnectorType.Social,
  configGuard: mySocialConnectorConfigGuard,
  getAuthorizationUri: getAuthorizationUri(getConfig),
  getUserInfo: getUserInfo(getConfig),
  getTokenResponseAndUserInfo: getTokenResponseAndUserInfo(getConfig),
  getAccessTokenByRefreshToken: getAccessTokenByRefreshToken(getConfig),
});

export default createMySocialConnector;

```

Replace the `getIdToken` and `getUserInfoFromEndpoint` placeholders with actual HTTP implementations using your preferred client (e.g., `ky` or `fetch`).

## Key Reference Files in the Logto Repository

- **[`packages/connectors/connector-oidc/src/index.ts`](https://github.com/logto-io/logto/blob/main/packages/connectors/connector-oidc/src/index.ts)** – Full implementation of authorization URL construction, nonce handling, and ID-token verification.
- **[`packages/connectors/connector-oidc/types.ts`](https://github.com/logto-io/logto/blob/main/packages/connectors/connector-oidc/types.ts)** – Zod configuration guard patterns for OIDC providers.
- **[`packages/connectors/connector-oauth2/src/index.ts`](https://github.com/logto-io/logto/blob/main/packages/connectors/connector-oauth2/src/index.ts)** – Demonstrates custom `profileMap` for non-standard claim mappings.
- **[`packages/connectors/templates/sync-preset.js`](https://github.com/logto-io/logto/blob/main/packages/connectors/templates/sync-preset.js)** – Script that synchronizes [`package.json`](https://github.com/logto-io/logto/blob/main/package.json) templates across all connectors.
- **`packages/toolkit/connector-kit`** – Source of the `SocialConnector` interface, `validateConfig` utility, and error handling types.

## Summary

- **Custom social connectors** in Logto are self-contained packages under `packages/connectors/` that implement the `SocialConnector` interface.
- **Required components** include metadata, a Zod config guard, and four core functions for authorization and user info retrieval.
- **Security best practices** involve generating nonces in `getAuthorizationUri` and validating them in `getUserInfo` via the session helpers.
- **Registration** occurs automatically after running `pnpm prepack` and restarting the Logto Console, where admins configure the connector using your defined schema.

## Frequently Asked Questions

### What is the minimum code required to create a custom social connector?

You need three files: [`constant.ts`](https://github.com/logto-io/logto/blob/main/constant.ts) for metadata, [`types.ts`](https://github.com/logto-io/logto/blob/main/types.ts) for the Zod configuration guard, and [`index.ts`](https://github.com/logto-io/logto/blob/main/index.ts) exporting a factory function that returns an object with `metadata`, `type: ConnectorType.Social`, `configGuard`, `getAuthorizationUri`, and `getUserInfo`. The `getTokenResponseAndUserInfo` function is also required if you intend to store tokens for persistent API access.

### How does Logto validate the configuration for custom connectors?

Logto uses the `configGuard` property you export, which must be a Zod schema. The `validateConfig` helper from `@logto/connector-kit` checks user input against this schema when admins save the connector settings in the Console, throwing `ConnectorError` with `ConnectorErrorCodes.InvalidConfig` if validation fails.

### Can I reuse existing OAuth helpers when building a custom connector?

Yes. The `@logto/connector-oauth` package exports `constructAuthorizationUri` for building authorization URLs, and `@logto/connector-kit` provides `jwtVerify` for ID-token validation. The `connector-oidc` and `connector-oauth2` packages in the Logto repository demonstrate how to integrate these helpers into your implementation.

### Where should I store tokens returned by the identity provider?

Enable "Store tokens for persistent API access" in the Logto Console for your connector. When `getTokenResponseAndUserInfo` returns a `tokenResponse`, Logto encrypts and stores these tokens in the Secret Vault. You can retrieve them later via the Logto Management API or use them to make authenticated calls to the IdP's APIs on behalf of the user.