Skip to main content

29 Tekniikkaa

29Tekniikka logo
Laravel API:n kanssa voi tulla vastaan aika turhauttava tilanne.
Kirjautuminen toimii. Token on olemassa. Käyttäjä löytyy tietokannasta. Endpoint näyttää oikealta.
Silti API vastaa:
401 Unauthorized
Ensimmäinen ajatus on yleensä, että tokenissa on jotain vikaa.
Aika usein kannattaa kuitenkin pysähtyä ennen uuden tokenin luomista.
Laravel ei näe sitä, mitä kehittäjä näkee omassa frontendissään tai Postmanissa. Se näkee yhden HTTP-pyynnön. Jos siinä pyynnössä ei ole oikeaa autentikointitietoa, tai reitti käyttää eri guardia kuin oletetaan, lopputulos on sama: käyttäjää ei tunnisteta.
Tämän vuoksi API-autentikointia on helpompi selvittää pyynnön kautta kuin aloittamalla Laravelin asetuksista.

Katso ensin sitä pyyntöä, joka epäonnistuu

➠ Jos ongelma tulee selaimesta, avaa Network-välilehti.
➠ Ei vielä lähdekoodia. Ei .env-tiedostoa. Vaan juuri se request.
➠ Mitä headerissa näkyy?
➠ Jos sovellus käyttää Bearer-tokenia, pitäisi mukana olla esimerkiksi:
Authorization: Bearer eyJ…
➠ Jos sitä ei ole, ongelma on jo löytynyt.
➠ Token voi olla tallessa selaimessa, mutta se ei tarkoita, että frontend lähettää sen jokaisessa API-pyynnössä. Interceptor voi olla poistettu käytöstä, API-clientin asetukset voivat olla muuttuneet tai yksi uusi request voi käyttää eri client-instanssia kuin muut.
➠ Tällainen virhe ei vaadi Laravelissa mitään muutosta.
➠ Jos header puuttuu, backendia ei ole vielä mitään järkeä korjata.

Yksi toimiva ja yksi toimimaton endpoint

Tämä on usein paljon hyödyllisempi vertailu kuin koko autentikointikonfiguraation läpikäynti.
Oletetaan, että:
GET /api/profile
toimii, mutta:
GET /api/orders
antaa 401:n.
Tokenin vaihtaminen ei olisi ensimmäinen asia, jota kokeilisin.
Jos sama käyttäjä pääsee ensimmäiseen endpointiin samalla autentikointitiedolla, tokenin toimivuudesta on jo jonkinlainen näyttö.
Silloin katsoisin /api/orders-reittiä.
Onko middleware sama?
Onko route varmasti saman API-version alla?
Käytetäänkö siinä samaa guardia?
Isommassa Laravel-sovelluksessa tällainen pieni ero voi jäädä yllättävän helposti huomaamatta, etenkin jos reittejä on kertynyt projektiin usean vuoden aikana.

Guard on yksi niistä kohdista, joissa Laravel-projekti voi muuttua sekavaksi

Pienessä projektissa autentikointi on yleensä helppo hahmottaa.
Kun sovellukseen tulee enemmän käyttäjätyyppejä, admin-paneeli, SPA, erillinen mobiilisovellus ja integraatioita, autentikointiin voi tulla useampi tapa.
Silloin esimerkiksi tämä:
Route::middleware(‘auth:sanctum’)->group(function () {
// …
});
ei ole vain tekninen yksityiskohta.
sanctum kertoo, millä tavalla Laravel yrittää tunnistaa käyttäjän.
Jos toinen osa sovelluksesta käyttää eri guardia, sama token ei välttämättä tarkoita siellä samaa asiaa.
Tämän vuoksi 401-virheen yhteydessä kannattaa oikeasti katsoa route-määrittely. Ei vain olettaa, että kaikki /api-reitit käyttävät samaa autentikointia.

Sanctum ja SPA tuovat mukaan selaimen

➠ Laravel Sanctumista puhuttaessa on hyvä erottaa kaksi asiaa.
➠ Toisessa tapauksessa API käyttää Bearer-tokeneita. Toisessa Laravelin kanssa toimiva SPA käyttää cookie-pohjaista autentikointia.
➠ Jälkimmäisessä selain on olennainen osa ongelmaa.
➠ Cookie voi olla väärälle domainille. Secure-asetus voi käyttäytyä eri tavalla HTTP- ja HTTPS-ympäristössä. SameSite-asetukset voivat vaikuttaa siihen, lähetetäänkö cookie lainkaan.
➠ Ja jos frontend sekä API ovat eri origineissa, CORS tulee mukaan.
➠ Silloin pelkkä Laravelin auth:sanctum-rivin katsominen ei kerro vielä kovin paljon.
➠ Jos autentikointi toimii paikallisesti osoitteessa esimerkiksi localhost, mutta tuotannossa frontend ja API ovat eri domaineissa, ympäristöjen ero kannattaa ottaa tosissaan. Se on paljon uskottavampi epäilty kuin ajatus siitä, että Laravel olisi yhtäkkiä unohtanut käyttäjän.

401 ja 403 kannattaa erottaa heti

Näitä virheitä näkee paljon yhdessä, vaikka ne kertovat eri asiasta.
401 tarkoittaa käytännössä, että pyyntöä ei hyväksytty autentikoidun käyttäjän pyyntönä.
403 taas kertoo, että käyttäjä tunnetaan, mutta toiminto ei ole hänelle sallittu.
Jos tavallinen käyttäjä yrittää avata admin-toiminnon ja saa 403:n, autentikointi on saattanut toimia aivan oikein.
Siksi käyttöoikeuslogiikkaa ei kannata lähteä tutkimaan silloin, kun käyttäjää ei ole vielä tunnistettu.
Pieni ero HTTP-statuksessa voi säästää melko paljon turhaa selvitystyötä.

Tuotannossa ongelma voi olla Laravelin ulkopuolella

Paikallisesti request menee usein suoraan sovellukselle.
Tuotannossa välissä voi olla Nginx, load balancer, CDN tai jokin muu proxy.
Se tarkoittaa, että kehittäjän lähettämä request ja Laravelin vastaanottama request eivät ole välttämättä käytännössä sama asia.
Tämä näkyy erityisesti silloin, kun Authorization-header ei päädy sovellukselle asti.
Jos Postmanilla suoraan palveluun tehty request toimii, mutta julkisen domainin kautta tehty request ei toimi, kannattaa tutkia liikenteen väliin tulevat komponentit.
Uuden tokenin luominen ei siinä tilanteessa korjaa mitään.

Jos ongelma alkoi deployn jälkeen

➢ Tämä on hyvä vihje, eikä sitä kannata ohittaa.
➢ Jos autentikointi toimi eilen ja uuden version julkaisun jälkeen kaikki suojatut endpointit palauttavat 401:n, kysymys kuuluu ensin:
Mitä deploy muutti?
  • Ei:
  • Esimerkiksi ympäristömuuttuja voi olla muuttunut. Config cache voi sisältää vanhan arvon. Middleware voi olla vaihtunut. Pakettiversio voi olla päivittynyt.
  • Jos Laravelin konfiguraatio on välimuistissa, .env-tiedoston nykyinen arvo ei välttämättä kerro sitä, mitä käynnissä oleva sovellus käyttää.
  • Tämä on yksi niistä tuotanto-ongelmista, joissa kehittäjä voi tuijottaa oikeaa tiedostoa ja silti etsiä väärästä paikasta.
Miten Sanctum asennetaan uudelleen?

Vanha token ei välttämättä ole enää käyttökelpoinen

Kaikki autentikointivirheet eivät ole ohjelmointivirheitä.
Token voi olla vanhentunut. Se voidaan olla peruttu. Käyttäjän käyttöoikeus voi olla poistettu.
Varsinkin pitkään auki olevissa selain- tai mobiilisovelluksissa tämä on täysin normaali tilanne.
Sovellus voi yrittää käyttää tunnistetta, joka oli täysin validi silloin, kun se luotiin, mutta ei enää ole.
Ongelma syntyy siinä vaiheessa, kun frontend ei osaa käsitellä 401-vastausta järkevästi.
Jos käyttäjä ohjataan kirjautumaan uudelleen, tilanne on selkeä. Jos sovellus yrittää samaa pyyntöä uudelleen samalla vanhalla tokenilla, ongelma vain jatkuu.

Retry ei ole ratkaisu autentikointivirheeseen

Tämä kannattaa sanoa erikseen.
Verkkoyhteyden tilapäiseen häiriöön retry voi olla hyvä ratkaisu.
401:n kohdalla automaattinen retry samalla tokenilla ei yleensä auta.
Jos palvelin kertoo, ettei tunnistetta hyväksytä, saman tunnisteen lähettäminen vielä kolme kertaa ei tee siitä validia.
Frontendissä pitäisi olla selkeä ero tilapäisen verkkovirheen ja vanhentuneen autentikoinnin välillä.
Tämä kuulostaa pieneltä asialta, mutta sillä on iso vaikutus käyttökokemukseen. Muuten käyttäjä voi jäädä tilanteeseen, jossa sovellus näyttää yrittävän jotain uudelleen, vaikka palvelin hylkää jokaisen pyynnön täsmälleen samasta syystä.

CORS ei ole Laravelin autentikointivirhe

➠ Tässä menee helposti kaksi asiaa sekaisin.
➠ Selain voi estää cross-origin-pyynnön ennen kuin varsinainen API-kutsu käsitellään.
➠ Jos Network-välilehdellä näkyy varsinainen API-pyyntö, joka palauttaa 401, kyse on eri tilanteesta.
➠ Näitä ei kannata korjata samalla tavalla.
➠ Sama pätee CSRF:ään. Laravelin selainpohjaisessa autentikoinnissa CSRF-suojaus on tärkeä, mutta erillisen Bearer-tokenilla toimivan API:n kanssa ongelma ei ole automaattisesti CSRF.
➠ Kun nämä käsitteet sekoittuvat, asetuksia alkaa helposti tulla lisää ilman, että kukaan enää tietää, miksi ne ovat mukana.

Jos autentikointi toimii Postmanilla mutta ei selaimella

Tämä on hyvä rajaus.
Backend ei ole silloin ensimmäinen epäilty.
Vertaisin Postmanin ja selaimen requestia.
Headerit.
Cookie.
Origin.
URL.
HTTP-metodi.
Jos käytössä on cookie-pohjainen autentikointi, katsoisin erityisesti sitä, lähteekö cookie selaimesta lainkaan.
Jos käytössä on Bearer-token, tarkistaisin Authorization-headerin.
Selaimen Network-välilehti on tässä paljon hyödyllisempi kuin frontendin oma “Request failed” -virheilmoitus.
Frontend kertoo usein vain lopputuloksen.
Network kertoo, mitä oikeasti tapahtui.

Älä tulosta tokenia lokiin

Älä tulosta tokenia lokiin
Tokenia ei kuitenkaan pidä kirjoittaa lokiin.
Jos Bearer-token päätyy lokiin, siitä tulee käytännössä ylimääräinen paikka, josta tunnistetieto voidaan myöhemmin löytää.
Parempi ratkaisu on logata pyynnöstä tekniset tiedot, joiden avulla ongelmaa voidaan seurata ilman varsinaista salaista arvoa.
Esimerkiksi request ID, endpoint ja HTTP-status voivat olla paljon hyödyllisempiä kuin itse token.
Sama varovaisuus koskee APM-järjestelmiä ja virheseurantaa. Autentikointitietoja ei pidä lähettää niihin vahingossa vain siksi, että debuggaus olisi helpompaa.

Joskus ongelma on käyttöoikeuksissa, ei autentikoinnissa

➠ Kuvitellaan, että käyttäjä pääsee APIin, mutta tietyn toiminnon kutsu epäonnistuu.
➠ Jos vastaus on 403, käyttäjä on todennäköisesti jo tunnistettu.
➠ Silloin kannattaa siirtyä seuraavaan kysymykseen: mitä tämä käyttäjä saa tehdä?
➠ Laravel-projektissa tämä voi liittyä esimerkiksi policyyn, gateen tai sovelluksen omaan roolilogiiikkaan.
➠ Tässä vaiheessa autentikointijärjestelmän muuttaminen olisi väärä korjaus.
➠ Käyttäjä on jo päässyt sisään.
➠ Häneltä vain puuttuu oikeus tiettyyn toimintaan.Häneltä vain puuttuu oikeus tiettyyn toimintaan.

API-version muuttaminen voi rikkoa vanhan clientin

Tämä näkyy erityisesti tuotteissa, joissa API:lla on useita käyttäjiä.
Selain käyttää uutta frontend-versiota. Mobiilisovellus voi edelleen käyttää kuukausia vanhaa versiota.
Jos autentikointiin tehdään muutos, joka toimii uudessa clientissä mutta ei vanhassa, ongelma voi näkyä vain osalla käyttäjistä.
Silloin “toimii minulla” ei kerro kovin paljon.
On tiedettävä, mitä API-versiota kyseinen asiakas käyttää ja millä tavalla se lähettää autentikointitiedon.
Tämä on yksi syy siihen, miksi autentikointimuutokset kannattaa käsitellä API:n yhteensopivuuskysymyksenä, ei vain Laravel-konfiguraation muutoksena.

Mitä tekisin ensimmäisenä tuotannossa?

Jos joku sanoo, että Laravel API:n autentikointi ei toimi, en aloittaisi paketista.
Aloittaisin yhdestä requestista.
Katsoisin statuskoodin.
Katsoisin, mitä asiakas lähetti.
Katsoisin, mikä route käsitteli pyynnön.
Sen jälkeen tarkistaisin middlewaret ja guardin.
Jos ongelma esiintyy vain selaimessa, vertaisin selaimen requestia toimivaan API-clientin requestiin.
Jos ongelma alkoi deployn jälkeen, katsoisin deployn mukana muuttuneet asiat.
Ja jos kaikki näyttää oikealta, vasta sitten menisin syvemmälle autentikointipaketin toimintaan.
Tässä järjestyksessä on yksi käytännön etu: jokainen vaihe poistaa yhden mahdollisen selityksen.
Ei tarvitse arvailla kerralla koko järjestelmää.

Autentikointiongelman korjaaminen ei tarkoita aina uuden ratkaisun rakentamista

➠ Laravel API:n kanssa tulee helposti sellainen olo, että jos 401 ei poistu heti, autentikointiratkaisu on jotenkin väärä.
➠ Usein ei ole.
➠ Vika voi olla paljon pienempi.
➠ Yksi route käyttää väärää middlewarea.
➠ Frontend unohtaa Authorization-headerin.
➠ Cookie ei kulje tuotannossa.
➠ Config cache sisältää vanhan arvon.
➠ Token on vanhentunut.
➠ Tai käyttäjä on autentikoitu oikein, mutta endpoint palauttaa 403:n käyttöoikeuden puuttumisen vuoksi.
➠ Kun nämä erot tehdään näkyviksi, ongelman ratkaiseminen muuttuu paljon rauhallisemmaksi työksi.
➠ Ei tarvitse vaihtaa koko autentikointijärjestelmää yhden virheilmoituksen perusteella.
➠ Laravel API:n autentikoinnissa tärkein kysymys ei lopulta ole “miksi token ei toimi?”.
➠ Parempi kysymys on:
  • Kun siihen löytyy vastaus, varsinainen korjaus on yleensä paljon lähempänä kuin ensimmäinen 401-virhe antaa ymmärtää.
Missä kohtaa tämä yksi pyyntö poikkeaa toimivasta pyynnöstä?