Галоўная / Артыкулы

Стварэнне аутентыкацыі JWT гатовай да выкорыстанняў у API на Node.js

Як хэшаваць паролі, выдаваць тымчасовыя JWT-токены, дадзіць токены апдэтаў і механізмы анулювання, аддзельваць процес автарызаціі, заходзіць ад атак сіловага давяння і пераканацца, што автарызація правильна не вядома.

Выдавец: REACTAPP.TOP13 хв чытання2888 слоў

Крыніцы: JWT Authentication in Node.js: What I Learned Building Secure APIs

У дасяжынай документацыі аутантыка паказваецца ў адной лініі: выкорыстоўчык заходзіць праз электронную пашту і пароль, а API надае доступ. Але як толькі ёё рэалізуюць, гэтая лінія ператвараецца на дзесяткі пытанняў. Дзе зберагаюцца паролі і як яны пераканальваны? Як API адгукваецца на кожны запит? Што выходзіць, калі токен заканчываецца свой тэрмін, як функцыонуюць токены апавяржэння, і што павінна рабіць система пасля серыі няудачных спроб заходжання? Пад усімі гэтымі пытаннямі лежыць найважлівейша: як не дазволіць самой системе заходжання стаць слабейшым элементам прыкладнага програма?

У гэтым кялікаванні рассмотрваюцься гэтыя пытанні для API на Node.js і TypeScript, якое падтрымваецца PostgreSQL; аутантыка рассматрываецца не як отдэльны канцэнтры, а як слой безпекі, який абгортае всю прыемку. До канцала вы будете матыць чыстая картіна кожнага слоя, проблемы, якія можуць выйсці на кожным з іх, а таксама чарткі для выявлення моменту, калі система справды гатовая да выводу ў працэўную среду.

Храніце хэшы пароў, а не самыя паролі

Першая правіла — пароль не ёсць звычным дадзеннем. Яго ніколі не трэба зберагаць у чыстам текстовом формате. Запіс вядока, падобны таму, што прыведзен нижэй, є непрыемным, таму што адна-едынственная выцеклівая даных з базы гэтым чынам апавярніла б паролі всіх вядокаў, а ўрачыстаючы тое, што люди воспалоўваюць паролі, гэта таксама апавярніла б іх адныя ў іншых сервісах.

email: sachin@example.com
password: MyPassword123

У замест на гэта, пры зберагаччы кожны пароль трэба працавац з намерна медзкім алгорытамом хэшавання пароў. З пакетам bcrypt вы імпортуеце гэтую бібліятэку:

import bcrypt from "bcrypt";

і хэшуеце пароль пад час рэўістрацыі. Другі аргумент, 12, — это фактар коста: кожны падыш удвойвае колькісць неабходных вычысканняў, што робіць прабавкі без сеті проты вкрадзенага хэш-тэблу значна дорожэй, адночаса заставаючы процес дастатньна швыракім для адной логіну.

const passwordHash = await bcrypt.hash(password, 12);

Пад час заходжання не адгэнтрыруецца нічога. Сервер берае пароль, які ўжо адправіў корыстнік, і запытае bcrypt, чы роўнасць ён зямлёванаму хэшу. Функцыя bcrypt.compare чытае салт і фактар коста, ўбудованыя ў хэш, паўтарае вычыскання і вяртае булева значэнне.

const isValid = await bcrypt.compare(
  password,
  user.passwordHash
);

Ключовая адзынка заключаецца ў тым, што паролі хэшуюцца, а не зашыфровываюцца для пазнейшага вярнення. Пасля завершэння рэгістрацыі ваша прыкладна програма ніколі не павінна маты можлівасці або патрабаваць вярнення первіснага пароля. Две практычныя прыметкі: bcrypt уваажае толькі першыя 72 байты вхідных дадзенняў, таму чырвае дужа дугія паролі ўтайна адсечываюцца, а такія алгорытмы, як Argon2, ўжо ўсталяваныя альтэрнатывы, якщо вы хочаце варыянт, які складна адмахнуцца за дапамой памяці.

Спрыяйце заходжэнню як частыні рабочага процесу, а не запыту

Тыповы канцэнтр прадаўлення выконвае ряд крокаў, пачынаючы з прыемы запыту, пераходзячы через паверкі, пошук, порэванне і стварэнне токена:

Client
   ↓
POST /api/auth/login
   ↓
Validate input
   ↓
Find user
   ↓
Compare password
   ↓
Create authentication tokens
   ↓
Return authentication response

Важна тая частка, што кожны крок можа збыцца неудачным, і для кожнай неудачы патрэбна цялеспрабавная реакція. Электранаўпіс можа быць некоректны. Ніхто з корыстнікаў можа не падходзіць яму. Пароль можа быць некоректны. Аблікаванне можа быть заблокаванае. База дадзеных можа быць недоступная. Цэта ўпрабава з высокай ступенем сэнсыбілізаванасці да безпекі, і кожны ўзлок павінен быць спроектаваны, належна задаўаны ў журнале і працаваны.

Што такое JWT і чаго ён не ўсё

JSON Web Tokens — это распашчаны спосаб прызначэння аутентыфікованага ідэнтыфікатора ў API. Пасля успешнага входу сервер выдае падпісаны токен, які описвае корыстніка. Мінімальны пакет дадзеных можа мець толькі ідэнтыфікатор і ролю; sub — это стандартны элемент для ідэнтыфікатора корыстніка.

{
  "sub": "user-id",
  "role": "user"
}

Паллавед тэкст зашыфраваны, а не закрыты, таму хто бы ня было, у каго ёсць токэн, можа яго прачытаць. Ён должен мястіць толькі ідэнтыфікаторы і простыя даны, ніколі секрэты чы асобістыя даны. У наступных запитах кліент включае токэн у загаловак Authorization за дапамою схемы Bearer:

Authorization: Bearer <access-token>

Перад выдачай любага захіщанага ресурсу, бэкенд пераканальвае падпис токэна і час його выканчэння. Абсалютны порядак дзеяння выглядае так:

Login
  ↓
Credentials verified
  ↓
Access token issued
  ↓
Client sends token
  ↓
Server verifies token
  ↓
Protected API is allowed

Ключовы урок закладаецца ў тым, што сама по сабе JWT не робіць аплікацыю безпечной. Безпека таксама залежыць ад таго, як ствараецца токэн, дзе кліент яго зберагае, насколькі строго ён пераканальваецца (включаючы фіксацыю прыймаемага алгорытма падпису), калі ён выканчваецца, чы можа ён быць анульаваны, і як ён пераходзіць праз сеть (всегда через HTTPS).

Робіце токэны доступу короткага тыму жыцця

Токены доступу не должны застаўляцца нябясконечна. Якщо токен, які ўвесь час дзейсні, прасачыцца через файл логаў або заражаны прыстрой, зловмешчык можа выкарыстоўваць яго пра месцы.

У прыкладзе нижэй выкарыстоўваецца jsonwebtoken для падпісвання токена, які мае ідэнтыфікатор і ролю пользователя, з секретным ключам, які чытаецца з сераўа, і значэнне expiresIn задаецца на пятнаццац хвілін. Знак ! пасля process.env.JWT_SECRET ў TypeScript выражае асэрцыю, што зменная не ў значэнні null; у продакшн-кодзе безпечнае будзе пераканацца пры запуску, чы рэальна такая зменная, а не асэрваваць яе ў кожны момент выклікання.

const accessToken = jwt.sign(
  {
    sub: user.id,
    role: user.role
  },
  process.env.JWT_SECRET!,
  {
    expiresIn: "15m"
  }
);

Правильны тымп жыцця залежыць ад вашых трэбаванняў да безпекі, але прынцып застаёцца той самы: неабходна обмежыць час, пры якім будь-які кредытны дакумент, який дае доступ да захаваных ресурсаў, застаёцца выкарыстоўным. Короткі тымп жыцця значыць, што вкрадзены токен быстра стане безцэнным.

Токены апавяржэння рашаюць проблему выкарыстоўнасці

Якщо токены доступу выгораюць кожныя падзесят мінут, корыстувачы былі бы змушаны постаўляйць логін і пароль знову і знову, чаго ніхто не тольеруе. Токены апавяржэння рашаюць гэтыя проблемы. Пад час заходжання ў систему кліент атрымляе два крэдытныя данні: токен доступу з короткім тэрміном дзейнасці, які викорыстоўваецца пад час запытоў да API, і токен апавяржэння з дыяўальным тэрміном, які служыць выключна для отрымання новага токена доступу, калі стары выгорае.

┌─────────────────┐
             │      Login      │
             └────────┬────────┘
                      ↓
             ┌─────────────────┐
             │ Access Token    │
             │ Short Lifetime  │
             └────────┬────────┘
                      │
                 API Requests
                      │
                      ↓
              Token expires
                      │
                      ↓
             ┌─────────────────┐
             │ Refresh Token   │
             │ Longer Lifetime │
             └────────┬────────┘
                      ↓
             New Access Token

Такое раздзеленне дазволяе кантэкстным токенам заставацься короткімі, пры тым як сесіі застаюцца неперывнымі. Гэта таксама стварае новую адпаведнасць, калі таму токен на паўнай замене стае болей цэнным атрыбутам аўтанацыі. Яго неабходна аберагаць ад розгляду (зазвычай у httpOnly, Secure куку на вебе, а не ў месцах зберагчыка, доступных для JavaScript), апрашваць толькі да пункту на паўнай замене, а серверы неабходна строга яго перакантролюваць. Для глыбэйшага аналізу дизайну, адзірніце нашыя інструкцыі па стратэгіях токена на паўнай замене для систем аўтанацыі Node.js.

План анулювання

Адзін з якостей самастатковых токенаў, які дзівуе багато команд, — гэта тое, што яны застаюцца чыннымі пасля змян на сервере. Разглядзім корыстувальніка, які выйшаў з системы, адміністратара, які выключыў абліковач, токен апавяржэння, пра які вядома, што ён скомпрометаваны, або інцыдент у сфере безпекі, які вымагае завершэння кожной сесіі. Якщо проект не мае механізма анулювання, выдаленне чы рэгулярнае адкорекцыяя рядка базы дадзеных нічога не прабачае токану, які вже быў выданы; ён будзе працаваць да самага заканчэння тымчасовага періоду чыннасці.

З гэтай прычыны багатыя системы аутентыкацыі зберагаюць на стороне сервера записі пра токены апавяржэння чы сесіі, часта по аднам на кожны прыстрой, які асоціюецца з кожным корыстувальнікам:

User
 ├── Refresh Token A
 ├── Refresh Token B
 └── Refresh Token C

Калі такі правілы вжываюцца, сервер можа пазначыць адзінкавыя токены як анульованыя, завершыць усі сесіі для пользователя і адмовіцца ствараць новыя токены доступу на адміністрацыю з анульованага токена павтоновлення. Цэўказвае на справядлівы компроміс: JWTs зменшаюць колькасць інформацыі пра сесію, яку сервер павінен зберагчы, але безпечная аутентыкацыя зазвычай все ж такі выклекае пэўную інформацыю на стороне сервера. Короткі трымачы токенов доступу плюс токены павтоновлення, якія можна анулюваць, — это распашчаны спосаб балансавання гэтых двух фактораў.

Забезпечыць аутентыкацыю ў мідлвэре

Павтарэнне пераканальвання токеноў у кожным контролеры є прычыной багоў; адна занедбаная пераканальванне заставляе канцэнтрык відкрытым. Мідлвэр централізуе гэтую логіку. У Express захіщаны маршрут проста размешчае функцію authenticate пры своему контролеры:

app.get(
  "/api/profile",
  authenticate,
  profileController
);

Гэты мідлвэр выкананы невялікую, чыста апісаную задачу:

  1. Чытаць загалоўку Authorization.
  • Выявіце токен з значэння Bearer.
  • Пераканацеся ў правамільнасці падпісу і терміне дзейнасці.
  • Выявіце, якога пользователя представляе токен.
  • Прыўяжыце гэтую ідэнтычнасць да об’екта запиту для пазнейшага обробкі.
  • Адхіліцьце запит з адказам пра адказ, якщо хоць які крок зазнае нявясковасці.
  • Рэзультатны прабег кантролю лёгкі для разумення: невялідны або відсутны токен выдае код 401, а толькі вялідны токен доходзіць да кантролера.

    Request
       ↓
    Authentication Middleware
       ↓
    Token valid?
      ↙       ↘
     No        Yes
     ↓          ↓
    401       Controller
                 ↓
              Response
    

    Аутентыкацыя і автарызацыя — гэта разныя перакананні

    Этыя два тэрміна часта выкорыстоўваюцца як заменныя, але яны адпаведзаюць на разныя запытанні. Аутентыкацыя спрашвае, хто ўчаснік. Автарызацыя спрашвае, што гэты учаснік мае право робіць. Наша стаття пад там, дзе аутентыкацыя і автарызацыя паслужаюць у вашам коде болей дакладна розглядае гэтыя межы.

    Уявіце сабе прыклад аплікацыі з трыма ролямі:

    Admin
    Staff
    User
    

    Усе учаснікі трох прызначэнняў можуць быць цалкам аутентыфікованыя, пры тым як іх правыя адміністрацыі сутэчна разныя. Звычайнаму корыстніку можа быць разрэшана чытанне яго прафіля, адзначэння проблем і стварэнне новых:

    GET /api/profile
    GET /api/issues
    POST /api/issues
    

    Адміністратару таксама можа быць разрэшана зміна контаў корыстнікаў і выдаленне проблем:

    PATCH /api/users/:id
    DELETE /api/issues/:id
    

    Зберагчы правільны JWT можна толькі падтвердзіць ідэнтыфікацыю; гэта не дае права на адклёнчанне будзь-яких операцый. Безпечны бэкенд патрабуе як аутентыкацыйны мідлвэр, так і пераконтроль ролей чы праваў па кожнаму маршруту, а таксама, як можна, пераконтроль прыналежнасці, каб корыстнік могаў рэдагаваць толькі свою проблему, а не чыё-інша.

    Захаванне ад падзейваючыхся нявялікіх спробы аутентыкацыі

    Дзяржаўнае адлегчэнне аутентыкацыі павінна врачаць уражальнікоў, а не толькі законных корыстнікаў. Автаматызаваны скрыпт можа без канца прымоўляць спробы запоўнення пароля, якщо толькі што-небудзь гэта не зупініць. Найпростейшая захоўка — цэльвік, який запускае тымчасовае блакаванне пасля калькі спробы нявялікіх:

    Attempt 1 → Failed
    Attempt 2 → Failed
    Attempt 3 → Failed
    Attempt 4 → Failed
    Attempt 5 → Failed
                  ↓
              Temporary lock
    

    У практыцы, залежна ад вашых трэбаванняў, можна сумаваць калькі мера:

    • Абмежэнне частоты запытанняў да тапа аутентыкацыі.
    • Тымчасовае блакаванне акаунтаў.
  • Пастышчае збільшэнне часу адлеглівання пасля кожнага няўспэху.
  • Следзіць за падозрэльнымі законамасцямі.
  • Захопы, базаваныя на IP-адрэсе.
  • Апавешчэння пра падзеі, значныя для безпекі.
  • Выбірайце адзяйна, таму што захоп можа стаць средствам атакі типу denial-of-service. Якщо хто-небудзь можа заблокаваць аблікэцыю проста пасля пяці неправільных введэнняў е-пашты, атакавальнікі могу намерна заблокаваць справжніх корыстувачоў. Мэтай не є толькі блакаванне пасля няўспэшных спроб, але і зробленне автаматызаваных атак дорогіми, пры чым законныя корыстувачы все ўсё могу зайсці, напрыклад, шляхом супакоўвання часу адлеглівання па аблікэцыях з лімітамі частоты па IP-адрэсах, а не толькі прымусовага блакавання.

    Ніколі не давайце веры ідэнтыфікацыі, якую падае кліент

    Апасны законамасць — верыць у данні толькі таму, што фронтэнд іх апрацаваў. Падазроўваеце, што тэла запыту павідамляе ідэнтыфікатор корыстувача та його ролю:

    {
      "userId": "123",
      "role": "admin"
    }
    

    Нічыта не спяшае злонамерованага кліента ад рэдагавання гэтых поль. Бэкенд ніколі не должен прыяжджаць, што гэтыя данні ўсё правыя. Айдентыка павинна выходзіць з аутэнтыфікацыйных дакументаў, якія сам сервер прабавіў, тое значыць, ідэнтыфікатор пользователя і яго ролю чытаюць з прабаванага токена (або з базы дадзеных за дапамогою інформацыі з токена), а перакананні ў правах базуюцца на гэтым:

    Request
       ↓
    Verify JWT
       ↓
    Read authenticated user ID
       ↓
    Check permissions
       ↓
    Access resource
    

    Сервер ёсць вышэйшая адаважнасць. Гэты прынцып распрастраняецца далей за межы аутэнтыфікацыі: кліент можа паспрабаваць падняць кантрольны досвід, сховаўшы кнопкі, якія корыстувач не можа выкарыстоўваць, але на яго ніколі не можна рэлявавацца ў пытанні адбывання правіл безпекі.

    Утримайцеся ад выліву інформацыі ў паведамленнях пра адказы

    Безпека стосуецца нават дробных деталей, такіх як формуліраванне адказаў пра адмоўы. Поручце адказ, у яком паведамляецца, што такога электроннага паштовага адказу няма:

    Email does not exist.
    

    з адказам, які навмысна застаецца нечыткім:

    Invalid email or password.
    

    Першы элемент паведамляе зловмесніку, якія электронныя падаклады зарэўнаваны, што дазволяе аблікаваць іх і спрыяе лёгкаму адгадванню пароляў. Другі элемент дае законным корыстнікам можлівасць спробаваць знову без падтверджэння чаго-лібо. Памятаце, што аблікаванне таксама можа выйсці ўнаследак часу адпаведзей або через тэрміналы рэўнавання і перазначэння пароля, таму тут таксама трэба прыкладваць адпаведную атрыбутыўнасць.

    Шырэйшая правіла таксама распрацоўвае бяговыя прычыны ў базе дадзенаў і неконтрольваныя асключэнні. Кліентам патрэбныія бяговыя прычыны, на якія ўжо можна дзеяць; ім не патрэбныя стэк-трейсы, сообщенні SQL чы внутранія ідэнтыфікаторы.

    Не павінны быць секрэты ў коде выканання

    Аутентыкацыя залежыць ад секрэтных дадзеных, такіх як ключы падпісаў, стрэнгі з’яўлення ў базе дадзенаў і ключы API трэціх сторон:

    JWT_SECRET
    DATABASE_URL
    API_KEY
    

    Їх ніколі не трэба дадзіць у репазітарые. Ключ, заданыяй без можлівасці змены, стае аб’язкам, як толькі хтось іншы можа прачытаць код:

    const secret = "my-super-secret-key";
    

    Чытанне яго з аптэкі сяродовысці — гэта мінімальны спосаб змягчыць проблему:

    const secret = process.env.JWT_SECRET;
    

    У рэжыме працы трэба пайсці секреты через правильную канфігурацыю або систему калектравання секрэтных дадзеных. Гэта особаўна важна для публічных репазітарыяў, дзе адны разы дадзены ключ можа момантальна прывести да серьёзнага інцыдэту; як толькі гэта адбудзецца, трэба заменіць секрет, а не проста выдаліць даныя з коміту.

    Тэставанне таго, калі аутентыкацыя не выйшла

    Ручныя тэсты непадходзяць для аутентыкацыі, таму што важлівыя ситуацыі — гэта тые, калі аутентыкацыя не должна выйсць. Базавы автаматызаваны набор тэстаў должен пакрыць як успешныя, так і адхіленыя сценарыі ў кожным элементе процесу.

    Рэгістрацыя

    Успешныя рэгістрацыі — гэта тые, якія пройшлі пераканаленне, тады калі ўскладненыя адресы электронной пошты, некоректныя данні і дублікаты абліков адхоўваны:

    Valid registration → Success
    Invalid email → Rejected
    Weak/invalid input → Rejected
    Duplicate account → Rejected
    

    Уваход

    Успешны ўвайшчыць з правильнымі данымі; адхоўваны некоректныя паролі, невядомыя аблікі і заблокаваныя аблікі:

    Correct password → Success
    Wrong password → Rejected
    Unknown account → Rejected
    Locked account → Rejected
    

    Захаваныя маршруты

    Відсутныя, некоректныя і выйшлы з часу давальніцтва токены даўаюць код 401; кантрольны токен дазваляе пройсці, а кантрольны токен без неабходнай ролі даўае код 403. Зважайце на разліку: 401 означае "мы не ведаем, хто вы," тады калі 403 означае "мы ведаем, хто вы, а адказ — няма">.

    No token → 401
    Invalid token → 401
    Expired token → 401
    Valid token → Allowed
    Valid token + insufficient role → 403
    

    Такія тэсты не толькі паўтараюць, што автентыкацыя працуе, але і паказваюць, як ёна можа злучыцца, і самэ гэта месца, дзе часта хаваюцься справжнія вразломы.

    Модэль автентыкацыі з шарамі

    Дапамагаець уявіцца аутентыкацыю як стакан з разнымі шарамі, кожны з якіх мае свойя завадзівы: ад пераканання ў правамах на доступ да пераверкі пароля, выдачы токенаў, параболізацыі та автарызацыі токенаў да самага захаванага ресурсу:

    ┌─────────────────────────────┐
    │       User Credentials      │
    └──────────────┬──────────────┘
                   ↓
    ┌─────────────────────────────┐
    │     Password Verification   │
    └──────────────┬──────────────┘
                   ↓
    ┌─────────────────────────────┐
    │       Token Issuance        │
    └──────────────┬──────────────┘
                   ↓
    ┌─────────────────────────────┐
    │      Token Validation       │
    └──────────────┬──────────────┘
                   ↓
    ┌─────────────────────────────┐
    │       Authorization         │
    └──────────────┬──────────────┘
                   ↓
    ┌─────────────────────────────┐
    │       Protected Resource    │
    └─────────────────────────────┘
    

    Навакол усьго стакана знаходзіцца шар, які дапамагае ў всіх аспектах: манітарынг, логаванне, обмежэння частоты запытоў, безпечная настройка і тэставанне.

    Спіс перакананняяў для прыемлівасці ў працэсе

    Перш чым сказаць, што система аутентыкацыі готовая, пераканайцеся, што:

    • Паролі параболізуюцца за дапамогою моцнага, повольнага алгорытму.
    • Токены доступу маюць адпаведны, короткі тэрмін дзейнасці.
    • Токены апавежэння зберагаюцца, транспортууюцца і параболізуюцца безпечна.
    • Кожны захаваны маршрут пераканваецца ў правамах на доступ.
    • Автарызацыя выкананая як окремы крок пасля аутентыкацыі.
  • Практыкі неудачных спроб заходу блакуецца, без выкорыстання можлівасці зловжывання блакаваннем.
  • Секрэты зберагаюцца праздалей ад коду і карацца безпечна.
  • Логі не маюць пароляў, токенаў чы іншых чутлівых дадзенняў.
  • Адпаведзі на аберанні не адкрываюць няпатрэбных внутраніх дакладнасцей.
  • Працэсы аутентыкацыі перакантролююцца за дапамою аўтаматызаваных тэстаў.
  • Анулюванне токенаў і сэсій планаваецца заздалегідь, а не відкладаецца.
  • Канфігурацыя для працы ў рэальных умовах была пераглянутая.
  • Гэты спіс ў дзесяткі разоў лепшы крок для контролю, чым проста перакананне, чы работае захід.

    Галоўныя выводы

    Автанацыя — гэта не тое, што можна дадзіць напрыканце проекта. Яна вплывае на дыяграму базы дадзеных, структуру API, мідлвэр, працэю фронтэнда, абаране няправильных дзейсціб, логаванне, тэставанне і развяроце, а галоўнае — на безпеку. Канцэнтры для заходжання ў систему можна напісаць за колькае рэчы; але система, якая будзе працаваць безпечна, калі з’являюцца рэальныя корыстувачы, хакеры, прыканчаныя токены, неправільныя запиты і проблемы ў рэальных умовах, — гэта значна сложнейшая інжынерная задача.

    Наступныя аспекты, якія варта дакладна выявіць, — это безпека токенав для аднавлення: дзе іх трэба зберагчы, чы і як іх мяняць, як выявіць павторны вжытак старога токена, як у практыцы выглядае анулювання, і як должна працаваць функцыя выходу з системы на разных прыстроях. Кожна з гэтых рашэнняў паходзіць з таго ж прынцыпа, што і всё вышэй: безпека захоўваецца, калі яна є часткай кожнага дызайнавага выбору, а не шаром, які дадаецца пазней.