Как сделать авторизацию в API Next.js с использованием middleware?

Делаю авторизацию в приложении Next.js TypeScript. В куки хранится информация об аккаунте текущего пользователя, в том числе sessionId. В файле api.ts есть запросы на определенный адрес, которые нужно перехватывать в middleware и добавлять к заголовку запроса сессию в формате 'X-Auth-Token': account.sessionId. Если ответ на запрос возвращается с кодом 403, то нужно вернуть пользователя на страницу авторизации.

Файл middleware.ts

import AccountService from '@/lib/account-service';
import { getCookies } from 'next-client-cookies/server';
import { NextResponse } from 'next/server'
import type { NextRequest} from 'next/server'

export async function middleware(request: NextRequest) {

const cookies = getCookies();
const account = new AccountService(cookies).get();

if (!account && !request.nextUrl.pathname.startsWith('/login')) {
    return NextResponse.rewrite(new URL('/login', request.url));
}

if (account && request.nextUrl.pathname.startsWith('/login')) {
    return NextResponse.redirect(new URL('/', request.url))
}
/*

if (account ) {
    const modifiedRequest = {
      headers: new Headers(request.headers),
    };

    modifiedRequest.headers.set('X-Auth-Token', account.sessionid);

    return NextResponse.next({ request: modifiedRequest });
}*/
}

export const config = {
  matcher: [
    '/((?!api|_next|favicon.ico).*)',
  ],
}

файл api.ts

import { faker } from "@faker-js/faker";
import { _arrayUnique } from "chart.js/helpers";
export default class Api {
public static SRV = new Api(process.env.API_ENDPOINT || '');
public static CLN = new Api(process.env.NEXT_PUBLIC_API_ENDPOINT || '');

public static SAE = new Api(process.env.A_API_ENDPOINT || '');
public static CAE = new Api(process.env.NEXT_PUBLIC_A_API_ENDPOINT || '');


private _endpoint: string;

private constructor(endpoint: string) {
    this._endpoint = endpoint;
}

public async fetchWidgetData(uuid: string, key: string): Promise<{ [key: string]: any }> 
{
    // MOCKUP DATA 
    const options = {
        responsive: true,
        plugins: {
            legend: {
                position: 'top' as const,
            },
            title: {
                display: true,
                text: 'Chart.js Line Chart',
            },
        },
    };

    const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];

    const data = {
        labels,
        datasets: [
            {
                label: 'Dataset 1',
                data: labels.map(() => faker.number.int({ min: -1000, max: 1000 })),
                borderColor: 'rgb(255, 99, 132)',
                backgroundColor: 'rgba(255, 99, 132, 0.5)',
            },
            {
                label: 'Dataset 2',
                data: labels.map(() => faker.number.int({ min: -1000, max: 1000 })),
                borderColor: 'rgb(53, 162, 235)',
                backgroundColor: 'rgba(53, 162, 235, 0.5)',
            },
        ],
    };

      return { options, data };
    }

public async fetchConsumersData(uuid: string, key: string): Promise<{ [key: string]: any }>  {
   
    try{
    const queryParams = {
        convertTimestamp: 'true',
        joinCampaignNames: 'true',
    };

    const url = `${Api.SAE._endpoint}/?${new URLSearchParams(queryParams).toString()}`;
    console.log(url);

    const data = await Api._run<IConsumersProps>(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                //'X-Auth-Token': sessionId,
            },
        }
    );
    return data ;
    } 
    catch (error) {
        console.error('Error during sign-in:', error);
        throw error;
    }
}

public async fetchInsight(uuid: string): Promise<IInsight> {
    // MOCKUP DATA 
    return {
        title: 'Global Insights',
        uuid,
        eventTypes: [
            { key: 'all-events', label: 'All events' },
            { key: 'purchase', label: 'Purchase' },
            { key: 'content', label: 'Content' },
            { key: 'misc', label: 'Misc' }
        ],
        layout: [
            {
                name: '', widgets: [
                    { key: 'event-type' }
                ]
            },
            {
                name: 'Overview', widgets: [
                    { key: 'campaign-details' },
                    { key: 'performance-trend' },
                ]
            },
            {
                name: 'Consumer Journey', widgets: [
                    { key: 'user-profiles' },
                ]
            },
        ]
    };
}

public async signIn(email: string, password: string): Promise<IAccount> {
    try {
        const url = `${Api.CAE._endpoint}/signin/remote`;
        const response = await Api._run<any>(
            url,
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    'login': email,
                    'password': password,
                }),
            }
        );

        if (response.result === 'AUTHENTICATED') {
            const account: IAccount = {
                id: response.advertiserData.guid,
                email: email,
                sessionid: response.advertiserData.sessionID,
                role: response.advertiserData.role,
            };
            return account;
        } else {
            throw new Error('Invalid login or password');
        }
    } catch (error) {
        console.error('Error during sign-in:', error);
        throw error;
        
    }
}

private static async _run<Type>(url: RequestInfo, params?: RequestInit) {
    
    const request = new Request(url, params);


    const resp = await fetch(request);

    if(resp.ok){
        const json = await resp.json();
        return json as Type;
    }
    else {
        if(resp.status === 403) { 
            throw new Error(`HTTP error! Status: ${resp.status}`);
        }
         else {
            throw new Error(`HTTP error! Status: ${resp.status}`);
        }
    }
        
}

}

Я совсем недавно начала погружаться в Next.js. Подскажите, пожалуйста, как это можно реализовать.


Ответы (0 шт):