Преди почти 3 години ZEIT пусна Next.js, минималистична рамка за изграждане на едностранични Javascript приложения по прост, но персонализиран начин. С фокус върху производителността и поддръжката извън кутията за изобразяване от страна на сървъра (SSR), те достигнаха над 280 000 седмични изтегляния на NPM и 40 000 звезди на GitHub. „Витрината на Next.js“ потвърждава успеха на рамката, която сега се използва от големи и малки компании, включително Netflix, Scale.ai, Marvel, Jet и дори „Auth0“.

Разгледайте Блога Auth0 🔐 и намерете всичко, което трябва да знаете за инфраструктурата за самоличност, управление на достъпа, SSO, JWT удостоверяване и най-новото в JavaScript. 👉 AUTH0 БЛОГ👈

Нови инструменти, нови предизвикателства

Предоставянето на решение за поддръжка на удостоверяване в Next.js беше „една от най-търсените функции“ в платформата. Но защо е така? Не можем ли да използваме някой от инструментите, които сме използвали толкова дълго в React и Node.js (напр.: passport, auth0.js, ...)? Next.js размива границата между фронтенд и бекенд, което прави съществуващата екосистема неоптимална, ако искате да използвате Next.js в пълния й потенциал.

Един пример е „Паспорт“, който зависи от наличността на Express. И въпреки че технически бихте могли да използвате Express във вашето приложение Next.js, това ще накара всички подобрения в производителността просто да изчезнат. Ако искате да оптимизирате за бързи студени стартове и искате да подобрите своята надеждност и мащабируемост, трябва да преминете към „модела за внедряване без сървър“.

Има различни начини за изграждане и внедряване на приложения с Next.js и в тази публикация в блога ще разгледаме тези случаи на употреба и ще обясним какъв механизъм можете да използвате най-добре за удостоверяване.

Какво означава удостоверяване за Next.js?

Когато създавате приложение Next.js, може да е необходимо удостоверяване в следните случаи:

  • При достъп до страница: „Моите фактури“
  • При достъп до API маршрут: /api/my/invoices
  • Когато вашето приложение извиква API, хостван извън вашето Next.js приложение, от името на потребителя: от www.mycompany.com до billing.mycompany.com/api

Сега, след като разбираме къде и кога нашето приложение може да изисква удостоверяване, нека проучим стратегиите за удостоверяване, които могат да бъдат приложени за различни модели на внедряване на Next.js.

Подход на статичен сайт Next.js

Next.js ви позволява да генерирате самостоятелно статично приложение без нужда от Node.js сървър. Когато стартирате next build, командата ще генерира HTML файлове за всяка страница, която я поддържа. Можете да използвате този генериран изход, за да внедрите сайта си във всяка статична хостинг услуга, като например Now, Amazon S3 или Netlify.

Тази техника може да се използва за генериране на пълни уебсайтове като статични сайтове, като публична начална страница на компания или когато създавате „администраторско табло“. Генерираният HTML може просто да бъде обвивката на вашето приложение — мислете за тази обвивка като за горен и долен колонтитул на вашето приложение. „Таблото за управление на ZEIT“ е един от най-добрите примери за това как може да изглежда това:

След като „обвивката“ бъде обслужена, клиентската страна ще извика необходимите API (носещи потребителската информация), ще извлече специфично за потребителя съдържание и ще актуализира страницата:

Този модел има няколко предимства, когато става въпрос за хостинг. Статичните хостинг сайтове (като Now, Amazon S3, Azure Blob Storage, Netlify и други) са тествани в битки, евтини са, но по-важното е, че са изключително бързи и работят добре с CDN.

Едно нещо, което ще бъде малко по-различно, е как се справяме с удостоверяването. Моделът, при който е наличен сървър, може да се справи с взаимодействието с Auth0 и да създаде сесия, но в този модел нямаме бекенд. Цялата работа се извършва във фронтенда:

  1. Потребителят се пренасочва към Auth0.
  2. Когато потребителят влезе успешно, той ще бъде пренасочен обратно към приложението.
  3. Страната на клиента ще завърши обмена на код с Auth0 и ще извлече id_token и access_token на потребителя, които ще бъдат съхранени в паметта.

Ако вашият случай на използване изисква динамично съдържание или съдържание, специфично за потребителя, ще трябва също така да внедрите нещо друго, като API. Този API няма да може да работи като част от вашия статичен хостинг сайт, така че това е мястото, където ще използвате платформа като AWS Lambda, Heroku или Now, за да го внедрите. След това клиентската страна ще говори директно с този API чрез предоставяне на access_token, ще извлече динамичното съдържание и ще обогати страницата, обслужвана от статичната хостинг платформа.

И това е много подобно на начина, по който се изгражда едностранично приложение, където приложението няма действителен „бекенд“, а вместо това извиква един или повече API. В общността ще намерите различни примери за това как да влезете в този тип приложение:

С use-auth0-hooks, например, е толкова лесно, колкото да конфигурирате приложението си така:

import { Auth0Provider } from 'use-auth0-hooks'; export default class Root extends App { render () { const { Component, pageProps } = this.props; return ( <Auth0Provider domain={'sandrino-dev.auth0.com'} clientId={'9f6ClmBt37ZGCXNqToPbefKmzVBSOLa2'} redirectUri={'http://localhost:3000/'}> <Component {...pageProps} /> </Auth0Provider> ); } }

След това можете да използвате React Hooks, за да извлечете потребителя и да поискате токен за достъп за един от вашите API. Отидете тук, за да научите повече за използване на Next.js в React приложения. След това access_token се изпраща, когато извикате вашия API, което прави следният пример чрез куката useApi:

import { useAuth } from 'use-auth0-hooks'; export default function MyShows() { const { isAuthenticated, isLoading, accessToken } = useAuth({ audience: 'https://api/tv-shows', scope: 'read:shows' }); if (!isAuthenticated) { return ( <div>You must first sign in to access your subscriptions.</div>; ) } if (isLoading) { return ( <div>Loading your user information...</div> ); } const { response, loading } = useApi( `${process.env.API_BASE_URL}/api/my/shows`, accessToken ); if (loading) { return ( <h1>Subscriptions for {user.email}<h1> <div>Loading your subscriptions ...</div> ); } return ( <h1>Subscriptions for {user.email}<h1> <div>You have subscribed to a total of {response && response.shows && response.shows.length} shows...</div> ); }

Какво точно се случва зад завесите тук?

Когато използва auth0-spa-js, потребителят ще влезе с помощта на Authorization Code Grant with PKCE. На високо ниво потребителят ще бъде пренасочен към Auth0, който ще обработва цялата необходима логика за удостоверяване и оторизация (регистрация, влизане, MFA, съгласие и т.н.). След като потребителят завърши процеса на удостоверяване с Auth0, той се пренасочва обратно към вашето приложение с Код за упълномощаване в низа на заявката.

Страната на клиента ще обмени този код за id_token и по избор access_token (1,2). След това access_token може да се използва за извикване на вашия API. Когато access_token изтече, същият поток ще се случи отново под завивките, като се използва <iframe>. Този подход за „безшумно удостоверяване“ ще продължи да работи, докато потребителят е влязъл – докато потребителят има сесия в Auth0. Когато сесията на потребителя в Auth0 изтече или се извърши излизане, това повикване ще бъде неуспешно и потребителят ще трябва да влезе отново.

Next.js модел за внедряване без сървър

Там, където Next.js блести, е в „модела за внедряване без сървър“, където всяка страница и API маршрут се разполагат като отделни функции без сървър, реализирани с помощта на ZEIT Now или AWS Lambda, например.

В този модел нямате работеща пълноценна уеб рамка (като Express.js), но вместо това средата за изпълнение ще изпълнява функции, като им предава заявка и обект за отговор ((req, res) => { }). Ето защо не можем да използваме традиционни уеб рамки (като Express.js) или който и да е от градивните елементи, които те предлагат за удостоверяване на потребители (като Passport.js) и създаване на сесии ( express-sessions).

Следната диаграма илюстрира как работи този модел: Next.js страниците и API маршрутите се изпълняват като отделни функции без сървър. Когато браузърът се опита да получи достъп до страницата Телевизионни предавания (1), функция ще се погрижи за изобразяването и обслужването на страницата, като ефективно изпълнява изобразяване от страна на сървъра. Тази функция също ще извика всички API, необходими за извличане на необходимите данни (2).

Ако целият сайт вече е зареден, всеки път, когато посетите друга страница, цялото изобразяване се извършва на клиента. В този момент всички извиквания на API се правят директно от браузъра. Както можете да видите, това е мястото, където линията между предния и задния слой започва да се размива.

Сега, преди да навлезем в каквито и да е подробности, важно е да изтъкнем, че има два специфични варианта на модела без сървър, когато става въпрос за удостоверяване, в зависимост от това къде трябва потребителят да бъде достъпен.

Без сървър с потребителя във фронтенда

Един вариант, който е много подобен на Статичния сайт, е показан на диаграмата по-долу. Всеки път, когато една страница трябва да бъде изобразена от страна на сървъра или когато се извика API маршрут, тези извиквания ще бъдат изпълнени в функция без сървър. В този модел удостоверяването се извършва от страна на клиента:

  1. Потребителят се пренасочва към Auth0
  2. Когато потребителят влезе успешно, той ще бъде пренасочен обратно към приложението
  3. Страната на клиента ще завърши обмена на код с Auth0 и ще извлече id_token и access_token на потребителя, които ще бъдат съхранени в паметта.

Всяка страница, изобразена от функцията без сървър, ще може да връща само съдържание, което е достъпно за всички потребители, без да е необходима каквато и да е форма на удостоверяване. След това, когато страницата се зареди, може да се изпълни някаква логика от страна на клиента, която ще извлече специфично за потребителя съдържание чрез извикване на API маршрути или чрез извикване на други API.

В диаграмата по-горе можете да видите пример как това може да работи:

  1. Страницата /account може да бъде изобразена от функция без сървър (SSR).
  2. На свой ред тази безсървърна функция също извиква /api/pricing-tiers API маршрута, който просто връща различните видове абонаменти, налични в приложението (например Безплатен, Разработчик, Корпоративен). Това е публична информация, така че тук не се изисква удостоверяване.
  3. Когато клиентската страна е готова, тя вече може да извика /api/billing-info API маршрута и да предостави токена за достъп на потребителя. След това клиентската страна може да рендира съдържание, което е специфично за потребителя

Само клиентската страна и маршрутите на API са наясно с потребителя, докато изобразяването на страници от страна на сървъра е в състояние да изобрази само публично съдържание (което е напълно добре за целите на SEO).

Без сървър с потребителя на бекенда

Вторият вкус в този модел е този, при който потребителят е необходим, когато страницата се изобразява от функцията без сървър. Когато това се случи, не можете да разчитате само на удостоверяване от страна на клиента

Тази диаграма е подобна на тази от предния модел, с изключение на няколко фини, но важни разлики:

  1. Страницата /account може да бъде изобразена от функция без сървър (SSR), но браузърът ще изпрати сесийната бисквитка.
  2. Тази безсървърна функция вече може също да извиква /api/billing-info чрез препращане на сесийната бисквитка, което прави възможно изобразяването на потребителско съдържание от страна на сървъра.
  3. Тази безсървърна функция също извиква /api/pricing-tiers API маршрута (нищо не се променя тук).

В този пример страницата на акаунта на потребителя може напълно да бъде изобразена от страната на сървъра.

Това, което също трябва да имате предвид, е случаят, когато сайтът вече е напълно зареден и потребителят навигира до страницата на акаунта. В този случай клиентската страна може директно да извика крайните точки и бисквитката автоматично ще бъде предоставена на API маршрута:

  1. Страната на клиента може да извика API маршрут, който изисква удостоверяване (тъй като сесийната бисквитка ще бъде предоставена автоматично от браузъра)
  2. Страната на клиента също ще извиква API маршрути, които не изискват удостоверяване

За да се приспособим към този случай на употреба, наскоро публикувахме версия за ранен достъп на @auth0/nextjs-auth0, която се грижи за удостоверяването в модела на внедряване без сървър, използвайки Authorization Code Grant. Този пакет също така създава сесия за удостоверения потребител, използвайки HttpOnly бисквитка, която смекчава най-честата XSS атака.

За да използвате библиотеката, ще започнете с инициализиране на екземпляр на SDK:

import { initAuth0 } from '@auth0/nextjs-auth0'; export default initAuth0({ domain: '<AUTH0_DOMAIN>', clientId: '<AUTH0_CLIENT_ID>', clientSecret: '<AUTH0_CLIENT_SECRET>', scope: 'openid profile', redirectUri: 'http://localhost:3000/api/callback', postLogoutRedirectUri: 'http://localhost:3000/', session: { cookieSecret: 'some-very-very-very-very-very-very-very-very-long-secret', cookieLifetime: 60 * 60 * 8 } });

След като бъде създаден екземпляр, вие ще добавите няколко API маршрута във вашето приложение Next.js, което ще обработва цялата необходима логика. Ето пример за манипулатора за влизане:

import auth0 from '../../utils/auth0'; export default async function login(req, res) { try { await auth0.handleLogin(req, res); } catch(error) { res.status(error.status || 500).end(error.message) } }

И това е! Вече можете да получите достъп до потребителя от страната на сървъра:

Profile.getInitialProps = async ({ req, res }) => { if (typeof window === 'undefined') { const { user } = await auth0.getSession(req); if (!user) { res.writeHead(302, { Location: '/api/login' }); res.end(); return; } return { user } } }

Чрез внедряване на Профил манипулатор, вие също ще имате крайна точка, която излага информацията на потребителя на страната на клиента. По този начин можете да извиквате маршрути на API във вашето приложение Next.js, без да се налага да се притеснявате за токени за достъп или нещо от това. Това е възможно, защото сесията на потребителя се съхранява в бисквитка, която се изпраща заедно с всяка заявка, която вашият клиент прави към вашия API маршрут.

async componentDidMount() { const res = await fetch('/api/me'); if (res.ok) { this.setState({ session: await res.json() }) } }

Обърнете внимание, че в този модел удостоверяването се извършва на сървъра, което означава, че клиентът всъщност не знае, че потребителят е влязъл. Можете да го уведомите, като предоставите тази информация в първоначалното състояние или чрез крайна точка, но няма да няма да излагате никакви id_token или access_token на клиента. Тази информация остава от страната на сървъра.

Какво точно се случва зад завесите тук?

Когато използва nextjs-auth0, потребителят ще влезе с помощта на Предаване на код за оторизация. На високо ниво потребителят ще бъде пренасочен към Auth0 (1,2), който ще обработва цялата необходима логика за удостоверяване и оторизация (регистрация, влизане, MFA, съгласие и така нататък), след което потребителят се пренасочва обратно към вашето приложение с Код за оторизация в низа на заявката (3).

Страната на сървъра (или по-добре функцията без сървър) ще обмени (4) този код за id_token и по избор access_token и refresh_token. След като id_token бъде валидиран, сесията ще бъде създадена и съхранена в шифрована бисквитка (5). Всеки път, когато се визуализира страница (от страна на сървъра) или се извика API маршрут, бисквитката на сесията ще бъде изпратена до функциите без сървър, които след това могат да получат достъп до сесията и до всяка съответна потребителска информация.

Извикване на външен API

Страниците и API маршрутите имат достъп до сесията на потребителя, но това не е случаят с външните API, които обикновено се хостват на други (под)домейни. Когато осъществявате достъп до тези API, ще трябва да им предоставите access_token, за да упълномощите потребителя.

Когато трябва да извикате външен API от името на потребителя, ще трябва да направите прокси това извикване чрез маршрут на API Next.js. Тези маршрути ще имат достъп до сесията на потребителя и в зависимост от това как потребителят е влязъл, тази сесия може да съдържа информацията за потребителя. По избор сесията може да има и следното:

Когато маршрутът на API на Next.js трябва да извика външен API от името на потребителя, той може да извлече access_token от сесията и да го добави към заглавката Authorization.

Следващият пример илюстрира как бихте създали API маршрут, който извлича access_token от сесията и след това го използва за извикване на API надолу по веригата.

import auth0 from '../../utils/auth0'; export default async function getBillingInfo(req, res) { try { const { accessToken } = await auth0.getSession(req); const client = new BillingApiClient(accessToken); return client.getBillingInfo(); } catch(error) { console.error(error) res.status(error.status || 500).end(error.message) } }

Най-голямата разлика с фронтенд модела е, че сървърът също ще знае, че потребителят е влязъл, когато изобразява страници. И като такава страна на сървъра няма да бъде ограничена до зареждане на публични данни, тя също така ще може да зарежда данни, специфични за потребителя, и да рендира тази информация от страна на сървъра.

Пълен пример може да бъде намерен в официалното хранилище Next.js.

Подходът на персонализирания сървър

Много често срещан (но наследен) модел на внедряване, който ще видите с Next.js, е мястото, където персонализиран сървър се използва за хостване на приложението Next.js. „Персонализиран сървър в Next.js“, който може да бъде реализиран с помощта на нещо като Express.js, приема заявката и я препраща към манипулатор на заявки, върнат от извикване на метода app.getRequestHandler(). С този подход персонализираният сървър може да действа като прокси и да има известна обработка, преди Next.js да обработи заявката:

Мидълуерите, които се изпълняват преди изобразяването от страна на сървъра Next.js, предоставят градивни елементи на вашето приложение като:

  • Удостоверяване
  • Сесии
  • Налагане на удостоверяване и оторизация
  • Ограничаване на скоростта

Всички градивни елементи и инструменти, които можете да използвате днес с Express.js, са достъпни за вас в този модел. Ето най-основния пример за това как бихте хоствали вашето Next.js приложение с Express.js:

const next = require('next'); const express = require('express'); ... const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare() .then(() => { const server = express(); ... passport.use(auth0); ... server.use(passport.initialize()); server.use(passport.session()); server.use(myApiRoutes); ... server.get('*', (req, res) => { return handle(req, res); }); server.listen(3000, (err) => { if (err) { throw err; } console.log('Listening on http://localhost:3000') }); }) .catch((ex) => { console.error(ex.stack); process.exit(1); });

Когато става въпрос за удостоверяване на потребителите в този модел, можете да използвате Passport.js (което е най-популярната рамка за удостоверяване в Node.js) в комбинация с passport-auth0. Когато потребителят влезе, ще бъде създадена сесия с помощта на express-session и след това се поддържа в браузъра с помощта на бисквитка HttpOnly.

След като потребителят има сесия, той ще може да осъществява достъп до страници или да извиква крайни точки на API, които изискват удостоверяване, използвайки маршрути на API на Next.js или традиционни крайни точки на Express. Сесийната бисквитка ще бъде изпратена заедно с всяка заявка, която автоматично ще направи потребителската информация достъпна от страната на сървъра.

В този модел вие основно изграждате обикновено уеб приложение, използвайки Node.js. Удостоверяването, достъпът до база данни и други функции вече са решен проблем.

Пълен пример за създаване на приложение Next.js с помощта на персонализиран сървър може да бъде намерен тук: Урок за удостоверяване на Next.js

Наследен модел?

Документите на Next.js вече не изброяват този модел, защото е най-малко оптималният от гледна точка на разходите и производителността:

  • Вие пропускате предимствата на „модела за внедряване без сървър“, като разпределени точки на отказ, безкрайна мащабируемост и ниска цена.
  • Не можете да генерирате статични сайтове, което може да е нещо, което искате, ако управлявате публичен уебсайт. Статичните сайтове са бързи и евтини за хостване.

Заключение

И с това покрихме различните модели на внедряване, които съществуват днес за рамката Next.js и обяснихме най-добрия начин за удостоверяване в тези модели и защо.

Ако този урок ви е помогнал по-добре да решите какво да използвате за вашия модел на внедряване, уведомете ни в коментарите по-долу!

Относно Auth0

Auth0, платформата за идентичност за създатели на приложения, предоставя на хиляди клиенти във всеки пазарен сектор единственото решение за идентичност, от което се нуждаят за техните уеб, мобилни, IoT и вътрешни приложения. Нейната разширяема платформа безпроблемно удостоверява и защитава повече от 2,5 милиарда влизания на месец, което я прави обичана от разработчиците и надеждна от глобалните предприятия. Централата на компанията в САЩ в Белвю, Вашингтон, и допълнителните офиси в Буенос Айрес, Лондон, Токио и Сидни поддържат нейните глобални клиенти, които се намират в над 70 държави.

За повече информация посетете https://auth0.com или следвайте @auth0 в Twitter.

Първоначално публикувано на https://auth0.com.