• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Express routing, middleware and request headers

0 głosów
106 wizyt
pytanie zadane 8 grudnia 2020 w JavaScript przez Jestem_Szaleńcem Użytkownik (530 p.)
edycja 8 grudnia 2020 przez Jestem_Szaleńcem

Hejka!
Próbuje napisać w expressie serwer który jak narazie ma zarejestrowane następujące funkcje

app.use(bodyParser.json())

app.use(setHeaders)

app.use(authRoutes)

app.use(isAuth)

app.use(usersRoutes)

app.use(errorHandler)

isAuth próbuje sprawdzić w nagłówkach requesta czy jest tam token autoryzacji jednak jeżeli znajduje się po authRoutes to nie moge w nim się dostać do nagłówków przez co wszystkie requesty do usersRoutes mają status 401.
Kod isAuth:

const jwt = require('jsonwebtoken')

const {JWT_SECRET_KEY} = require('../util/constants')

module.exports = (req, res, next) => {
  console.log(req.header('Authorization')) //undefined
  const token = req.header('Authorization')?.split(' ')[1]
  let decodedToken
  try {
    decodedToken = token && jwt.verify(token, JWT_SECRET_KEY)
  }
  catch(error) {
    error.status = 500
    throw error
  }
  if (!decodedToken) throw {status: 401, message: 'Not authenticated'}
  req.userId = decodedToken.userId
  next()
}



Wcześniej w authRoutes headery są normalnie widoczne :( jeśli usunę isAuth middleware to dalej w usersRoutes też mam dostęp do nagłówków. Czy ktoś mógłby mi wytłumaczyć dlaczego tak się dzieje? I w jaki sposób do wszystkich routów poza authRoutes dodać middleware sprawdzające uwierzytelnienie użytkownika?

komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

Możesz pokazać kod funkcji authRoutes?

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)

to express.Router() w którym definiuje endpointy

 

const express = require('express')

const authController = require('../controllers/auth')

const router = express.Router()

router.put('/new-user', authController.signup)

router.post('/login', authController.login)

module.exports = router

 

komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

Pokaż kod funkcji authController.signup i authController.login - może w nich modyfikujesz obiekt requesta?

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)

wydaje mi się że nie

const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')

const User = require('../models/User')
const {JWT_SECRET_KEY} = require('../util/constants')

exports.signup = async(req, res) => {
  const {email, username, password} = req.body
  const users = await User.find({$or: [{email}, {username}]}).exec()
  if (users.length) {
    const errors = {}
    users.forEach(user => {
      if (user.username === username) errors.username = 'Username already taken'
      if (user.email === email) errors.email = 'Email already in use'
    })
    throw errors
  }
  else {
    const hashedPassword = await bcrypt.hash(password, 12)
    await new User({email, username, password: hashedPassword}).save()
    return res.status(200).json({username})
  }
}

exports.login = async(req, res) => {
  const {username, password} = req.body
  const user = await User.findOne({username}, 'username password')
  if (!user) {
    throw {username: 'User not found', status: 401}
  }
  const passwordMatch = await bcrypt.compare(password, user.password)
  if (!passwordMatch) {
    throw {password: 'Invalid password', status: 401}
  }
  const userId = user._id.toString()
  const token = jwt.sign(
    {username, userId},
    JWT_SECRET_KEY,
    {expiresIn: '1h'}
  )
  return res.status(200).json({token, userId})
}

 

2 odpowiedzi

0 głosów
odpowiedź 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)
wybrane 8 grudnia 2020 przez Jestem_Szaleńcem
 
Najlepsza

Nie znam dogłębnie express'a, ale kojarzę, że jest tam problem z dostępem do nagłówków, gdy uprzednio zwróci się odpowiedź do klienta. Funkcje authController.signup i authController.login, albo rzucają błędem, albo zwracają wynik z wysłanego response - i w tym miejscu wg mnie tkwi przyczyna problemu. Jeśli te funkcje są używane jeszcze gdzieś poza middleware, to dla przypadku użycia w middleware nie odsyłaj odpowiedzi na tym etapie. Zamiast tego wywołaj next (dodaj go jako ostatni argument do nagłówków metod exports.signup oraz exports.login) żeby przekazać kontrolę następnemu middleware - wg kolejności powinno to być isAuth.

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)

Wielkie dzięki za odpowiedź :)

Jeżeli router nie dopasuje ścieżki do endpointu to wywołuje next() tak aby request mógł trafić do innych endpointów. Moje controllery tak jak powiedziałeś albo rzucają błędem co jest wyłapywane przez mój errorHandler albo zwracają response żeby zakończyć dany request i nie aktywować dalszych middlewarów / routerów. Wywołując next puścił bym request dalej a ja chce zakończyć go w tym miejscu dla żądań dotyczących tych endpointów 
 

router.put('/new-user', authController.signup)
 
router.post('/login', authController.login)

jeśli jednak expres w authRoutes nie znajdzie żądania o które chodzi w requescie to powinien wywołać isAuth (w którym jest next) żeby sprawdzić czy użytkownik jest uwierzytelniony dla wszystkich następych akcji w łańcuchu 

komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

A spróbuj w obu wspomnianych metodach przypisać headers'y do custom'owego property w obiekcie req, a potem odczytać to property w funkcji isAuth:

// wewnątrz obu metod dla middleware authRoutes
req._authHeader = req.header('Authorization');

// i potem w middleware isAuth
console.log('authHeader', req._authHeader);

 

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)
ale te metody nie wywołują się jeżeli uderzam w endpointy inne niż /new-user i /login tak więc req._authHeader też nie zostanie ustawione
komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

Jeśli metody z authRoutes nie będą wywołane, bo nie uderzysz do ich endpointów, to w isAuth powinieneś mieć normalnie dostęp do nagłówków - więc w czym jest teraz problem? :) Możesz rozpisać poszczególne przypadki: co i kiedy ma się dziać oraz które z nich nie działają?

W ogóle, to przy uderzaniu pod endpointy /login i /new-user nie powinien być dalej wołany middleware isAuth (bo w authRoutes nie ma wywołania next), więc w jakich przypadkach isAuth jest wołane? Tylko wtedy, gdy przesuniesz ten middleware przed authRoutes?

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)
edycja 8 grudnia 2020 przez Jestem_Szaleńcem

isAuth powinno być wywołane kiedy uderzam we wszystkie endpointy nie należące do authRoutes (np te w usersRoutes) ponieważ sprawdzanie uwierzytelnienia na drodze /login i /new-user nie ma sensu cheeky po zakomentowaniu całego kodu poza console.logiem i next w isAuth dowiedziałem się że isAuth wykonuje się 2 razy dla jednego requesta i za pierwszym razem nie posiada nagłówków (i jeżeli tutaj zrobię throw to jest koniec) a za drugim razem te nagłówki już ma surprise i ich wartość jest poprawna. Jednak używam isAuth tylko raz to  nie wiem dlaczego wywoływany jest 2 razy... podrzucam link do repo 
https://github.com/KubaWysocki/BlighterChat?fbclid=IwAR0kbtmK6etOMg4fIh6ovmpbSP1s-Lvx8vvACRgyPihcuWnSf5e5b280fuw

komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

To może zrób sobie middleware, w którym ręcznie sprawdzisz URL requesta i na tej podstawie ręcznie wywołaj te metody z authRoutes lub skorzystaj z biblioteki, np. express-unless.

po zakomentowaniu całego kodu poza console.logiem i next w isAuth dowiedziałem się że isAuth wykonuje się 2 razy dla jednego requesta i za pierwszym razem nie posiada nagłówków (i jeżeli tutaj zrobię throw to jest koniec) a za drugim razem te nagłówki już ma

A czy te requesty nie są z prefilght'em? To może powodować taki efekt.

komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)
Możliwe że requesty są z preflightem. Jednak ucząc się expressa dowiedziałem się że każdy request jest obsługiwany osobno i nie do końca wiem jak to obsłużyć. Czy da się ze strony backendu rozpoznać takie requesty? CORS mam skonfigurowane tylko przez własne middleware setHeaders. Słyszałem coś o paczce cors, możliwe że powinienem jej użyć. Jendak po stronie frontu mam w localstorage ustwiony token (chwilowo) no i wchodząc w swój frontend na strone /unauthorized moge wysłać requesta na  /login żeby się zalogować i w tym requescie jest header Authorization ponieważ wysyłam go w każdym requescie i moge go sobie wylogować za każdym razem. Po zalogowaniu przekierowuje na strone /profile która wysyła requesta na /users/:user i tutaj tak jakby odpalają się 2 requesty (2 console logi są) i pierwszy request ma nagłówek Authorization undefined a drugi już ma ten header z tokenem
komentarz 8 grudnia 2020 przez ScriptyChris Mędrzec (171,880 p.)

Sprawdź w devtoolsach przeglądarki (zakładka Network lub Sieć) czy został wysłany jeden request, czy dwa. Jeśli request nie spełnia kryteriów tzw. prostego-requesta, to będzie wysłany (wspomniany) preflight request, który poprzedzi ten właściwy request. Rozpoznać preflight request możesz np. przez sprawdzenie metody HTTP, której używa - powinno być OPTIONS.

1
komentarz 8 grudnia 2020 przez Jestem_Szaleńcem Użytkownik (530 p.)

wielkie dzięki za pomoc laugh nigdy wcześniej się z tym nie spotkałem ale chyba zawsze czy to w django czy gdzieś indziej używałem jakiś paczek do obsługi cors'ów devil To się chyba nazywa dług technologiczny XD jeszcze raz wielkie dzięki za pomoc!!!! Pozdrawiam angel

+1 głos
odpowiedź 8 grudnia 2020 przez Ehlert Ekspert (205,710 p.)
  1. Proponuję agregować poszczególne akcje w routery. Wtedy dla takiego routera możesz włączyć konkretny middleware. 
  2. Zamiast własnych funkcji skorzystaj z passporta.
  3. Ogólnie Express to praktycznie tylko biblioteka do routingu, nie dostarcza żadnych konwencji, struktury ani architektury. Jeśli nie jest to jakiś mikro projekt to polecam Nest.js.

Podobne pytania

0 głosów
0 odpowiedzi 506 wizyt
pytanie zadane 29 kwietnia 2017 w JavaScript przez moofi Początkujący (470 p.)
0 głosów
2 odpowiedzi 135 wizyt
pytanie zadane 4 grudnia 2019 w JavaScript przez VGB Początkujący (370 p.)
+1 głos
0 odpowiedzi 58 wizyt

86,541 zapytań

135,291 odpowiedzi

300,649 komentarzy

57,288 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...