Encrypting parameters

Learn how to encrypt parameters so that only certain values can be passed to generate your image.
Last updated on August 2, 2024
Og Image Generation

You can use the following code sample to explore using parameters and different content types with next/og. To learn more about OG Image Generation, see Open Graph Image Generation.

This is the directory structure for these files:

  • app/api/encrypted/route.tsx
    import { ImageResponse } from 'next/og';
    // App router includes @vercel/og.
    // No need to install it.
     
    const key = crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode('my_secret'),
      { name: 'HMAC', hash: { name: 'SHA-256' } },
      false,
      ['sign'],
    );
     
    function toHex(arrayBuffer: ArrayBuffer) {
      return Array.prototype.map
        .call(new Uint8Array(arrayBuffer), (n) => n.toString(16).padStart(2, '0'))
        .join('');
    }
     
    export async function GET(request: Request) {
      const { searchParams } = new URL(request.url);
     
      const id = searchParams.get('id');
      const token = searchParams.get('token');
     
      const verifyToken = toHex(
        await crypto.subtle.sign(
          'HMAC',
          await key,
          new TextEncoder().encode(JSON.stringify({ id })),
        ),
      );
     
      if (token !== verifyToken) {
        return new Response('Invalid token.', { status: 401 });
      }
     
      return new ImageResponse(
        (
          <div
            style={{
              display: 'flex',
              fontSize: 40,
              color: 'black',
              background: 'white',
              width: '100%',
              height: '100%',
              padding: '50px 200px',
              textAlign: 'center',
              justifyContent: 'center',
              alignItems: 'center',
            }}
          >
            <h1>Card generated, id={id}.</h1>
          </div>
        ),
        {
          width: 1200,
          height: 630,
        },
      );
    }

    If you're not using a framework, you must either add "type": "module" to your package.json or change your JavaScript Functions' file extensions from .js to .mjs

    Then, you need to create a frontend component that can take an id query parameter, which will be passed to the API route you created above.

    Create the dynamic route [id]/page under /app/encrypted and paste the following code:

    app/encrypted/[id]/page.tsx
    // This page generates the token to prevent generating OG images with random parameters (`id`).
    import { createHmac } from 'node:crypto';
     
    function getToken(id: string): string {
      const hmac = createHmac('sha256', 'my_secret');
      hmac.update(JSON.stringify({ id: id }));
      const token = hmac.digest('hex');
      return token;
    }
     
    interface PageParams {
      params: {
        id: string;
      };
    }
     
    export default function Page({ params }: PageParams) {
      console.log(params);
      const { id } = params;
      const token = getToken(id);
     
      return (
        <div>
          <h1>Encrypted Open Graph Image.</h1>
          <p>Only /a, /b, /c with correct tokens are accessible:</p>
          <a
            href={`/api/encrypted?id=${id}&token=${token}`}
            target="_blank"
            rel="noreferrer"
          >
            <code>
              /api/encrypted?id={id}&token={token}
            </code>
          </a>
        </div>
      );
    }

    If you're not using a framework, you must either add "type": "module" to your package.json or change your JavaScript Functions' file extensions from .js to .mjs

    Run your project locally and browse to http://localhost/encrypted/a(b or c will also work).

    Click on the generated link to be directed to the generated image.

    Image generated using

    /api/encrypted?id=a&token=634dd7e46ec814fb105074b73e26755b0d9966c031dca05d7e7cc65a1619058e

    In your actual implementation, you will use the code in /app/encrypted/[id]/page.tsx with a page to create your post html that will look like this.