{"id":80,"date":"2026-06-14T16:55:23","date_gmt":"2026-06-14T16:55:23","guid":{"rendered":"https:\/\/shattered.io\/fi\/2026\/06\/14\/turvalliset-sessiot-nodejs\/"},"modified":"2026-06-14T16:57:02","modified_gmt":"2026-06-14T16:57:02","slug":"turvalliset-sessiot-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fi\/turvalliset-sessiot-nodejs\/","title":{"rendered":"Turvalliset sessiot Node.js:ss\u00e4: 10 vaihetta [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Istunto eli sessio on se hauras lanka, joka pit\u00e4\u00e4 k\u00e4ytt\u00e4j\u00e4n kirjautuneena verkkosovellukseen pyynn\u00f6st\u00e4 toiseen. Jos lanka katkeaa v\u00e4\u00e4r\u00e4ll\u00e4 tavalla, hy\u00f6kk\u00e4\u00e4j\u00e4 voi varastaa toisen k\u00e4ytt\u00e4j\u00e4n istunnon ja toimia t\u00e4m\u00e4n nimiss\u00e4 ilman salasanaa. T\u00e4m\u00e4 opas rakentaa nollasta turvallisen, palvelinpohjaisen istunnonhallinnan <strong>Node.js 22 LTS<\/strong>&#8211; ja <strong>Express 5<\/strong> -ymp\u00e4rist\u00f6\u00f6n. K\u00e4ymme l\u00e4pi 10 konkreettista vaihetta, viisi sudenkuoppaa ja t\u00e4ydellisen toimivan esimerkkiprojektin, jonka voit kopioida suoraan tuotantoon.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OWASP listaa istunnonhallinnan virheet edelleen yhdeksi yleisimmist\u00e4 todennukseen liittyvist\u00e4 haavoittuvuuksista. Tyypilliset virheet ovat tuttuja: ev\u00e4ste ilman <code>HttpOnly<\/code>-lippua, istunto-ID:t\u00e4 ei uudisteta kirjautumisen yhteydess\u00e4, tai istuntovarasto pidet\u00e4\u00e4n palvelimen muistissa, jolloin se katoaa jokaisella uudelleenk\u00e4ynnistyksell\u00e4. Jokainen n\u00e4ist\u00e4 korjataan t\u00e4ss\u00e4 oppaassa. P\u00e4ivitetty 14. kes\u00e4kuuta 2026.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mita-palvelinpohjaiset-node-js-sessiot-ovat\">Mit\u00e4 palvelinpohjaiset Node.js-sessiot ovat<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Palvelinpohjaisessa istunnonhallinnassa selain saa vain pienen, satunnaisen istuntotunnisteen ev\u00e4steess\u00e4. Kaikki varsinainen tieto, kuten k\u00e4ytt\u00e4j\u00e4n ID, oikeudet ja kirjautumisaika, pysyy palvelimella istuntovarastossa. Selaimelle kulkee siis pelkk\u00e4 viiteavain, ei yht\u00e4\u00e4n luottamuksellista dataa. T\u00e4m\u00e4 on olennainen ero verrattuna tilattomaan JWT-malliin, jossa koko hy\u00f6tykuorma kulkee asiakkaan mukana allekirjoitettuna.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kun selain l\u00e4hett\u00e4\u00e4 pyynn\u00f6n, se liitt\u00e4\u00e4 mukaan istuntoev\u00e4steen. Express lukee tunnisteen, hakee vastaavan istunnon varastosta ja t\u00e4ytt\u00e4\u00e4 <code>req.session<\/code>-olion. Sovellus n\u00e4kee k\u00e4ytt\u00e4j\u00e4n tilan ilman, ett\u00e4 asiakas voi muokata sit\u00e4. Jos haluat kumota istunnon v\u00e4litt\u00f6m\u00e4sti, esimerkiksi k\u00e4ytt\u00e4j\u00e4n vaihtaessa salasanan tai havaitessasi tietomurron, poistat sen palvelimen varastosta ja yhteys katkeaa heti. Juuri t\u00e4m\u00e4 v\u00e4lit\u00f6n kumoamismahdollisuus tekee palvelinpohjaisista sessioista vahvan valinnan selainsovelluksiin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vertailun vuoksi: jos k\u00e4yt\u00e4t tilatonta tunnistautumista, tutustu erilliseen <a href=\"\/jwt-authentication-nodejs\/\">JWT-todennusoppaaseen<\/a>. Useimmissa selainpohjaisissa sovelluksissa palvelinpohjainen sessio on yksinkertaisempi ja turvallisempi oletusvalinta, koska kumoaminen ja istunnon uudistaminen ovat triviaaleja. Taulukko alla tiivist\u00e4\u00e4 erot.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ominaisuus<\/th><th>Palvelinpohjainen sessio<\/th><th>Tilaton JWT<\/th><\/tr><\/thead><tbody><tr><td>Datan sijainti<\/td><td>Palvelimella varastossa<\/td><td>Asiakkaan tokenissa<\/td><\/tr><tr><td>Ev\u00e4steen koko<\/td><td>Pieni (vain ID)<\/td><td>Suuri (koko hy\u00f6tykuorma)<\/td><\/tr><tr><td>V\u00e4lit\u00f6n kumoaminen<\/td><td>Kyll\u00e4, poista varastosta<\/td><td>Vaikea, vaatii estolistan<\/td><\/tr><tr><td>Skaalautuvuus monelle palvelimelle<\/td><td>Vaatii jaetun varaston (Redis)<\/td><td>Toimii ilman jaettua tilaa<\/td><\/tr><tr><td>Session fixation -riski<\/td><td>Estet\u00e4\u00e4n ID:n uudistuksella<\/td><td>Ei sovellu samalla tavalla<\/td><\/tr><tr><td>Tyypillinen k\u00e4ytt\u00f6kohde<\/td><td>Selainsovellukset, kirjautuminen<\/td><td>API:t, palvelujenv\u00e4linen liikenne<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"miksi-istuntoturvallisuus-on-tarkeaa-vuonna-2026\">Miksi istuntoturvallisuus on t\u00e4rke\u00e4\u00e4 vuonna 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Istuntoev\u00e4ste on k\u00e4yt\u00e4nn\u00f6ss\u00e4 avain, joka korvaa salasanan koko kirjautumisjakson ajaksi. Jos hy\u00f6kk\u00e4\u00e4j\u00e4 saa sen haltuunsa, h\u00e4n ohittaa salasanan, monivaiheisen tunnistautumisen ja kaikki muut kirjautumisen suojat kerralla. T\u00e4st\u00e4 syyst\u00e4 istunnon kaappaaminen on yksi tehokkaimmista tavoista vallata tili. Hy\u00f6kk\u00e4\u00e4j\u00e4t tavoittelevat ev\u00e4steit\u00e4 haittaohjelmilla, jotka lukevat selaimen tallennuksen, sek\u00e4 XSS-aukkojen kautta, jos ev\u00e4ste ei ole suojattu <code>HttpOnly<\/code>-lipulla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vuonna 2026 istuntoja varastavat tietovarkaat eli infostealerit ovat erityisen aktiivisia. Ne ker\u00e4\u00e4v\u00e4t selainten istuntoev\u00e4steit\u00e4 massoittain ja myyv\u00e4t niit\u00e4 pimeill\u00e4 markkinoilla, jolloin ostaja voi kirjautua suoraan uhrin tilille ilman salasanaa. T\u00e4m\u00e4 korostaa kahta asiaa, joita t\u00e4m\u00e4 opas painottaa: ev\u00e4ste pit\u00e4\u00e4 suojata niin, ettei sit\u00e4 saa luettua JavaScriptill\u00e4, ja palvelimella pit\u00e4\u00e4 olla kyky kumota istunto v\u00e4litt\u00f6m\u00e4sti, kun varkaus havaitaan. Palvelinpohjainen malli antaa juuri t\u00e4m\u00e4n kumoamiskyvyn, jota tilaton token ei tarjoa ilman lis\u00e4rakenteita.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00e4\u00e4ntely lis\u00e4\u00e4 oman paineensa. Suomessa ja muualla Pohjoismaissa GDPR edellytt\u00e4\u00e4 henkil\u00f6tietojen asianmukaista suojaa, ja NIS2-direktiivin my\u00f6t\u00e4 useat organisaatiot ovat velvollisia osoittamaan, ett\u00e4 niiden todennusratkaisut kest\u00e4v\u00e4t yleisimm\u00e4t hy\u00f6kk\u00e4ykset. Hyvin toteutettu istunnonhallinta, joka uudistaa istunto-ID:n, k\u00e4ytt\u00e4\u00e4 turvallisia ev\u00e4stelippuja ja s\u00e4ilytt\u00e4\u00e4 istunnot suojatussa varastossa, on osa t\u00e4t\u00e4 vaatimustenmukaisuutta. Huonosti toteutettu istunto taas voi johtaa tietomurtoon, ilmoitusvelvollisuuteen ja sakkoihin. T\u00e4m\u00e4n oppaan kymmenen vaihetta viev\u00e4t sinut perustasolta tuotantokelpoiseen, vaatimukset t\u00e4ytt\u00e4v\u00e4\u00e4n ratkaisuun.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esivaatimukset-ja-versiot\">Esivaatimukset ja versiot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4m\u00e4 opas olettaa, ett\u00e4 hallitset JavaScriptin perusteet ja komentorivin k\u00e4yt\u00f6n. Asenna alla luetellut ty\u00f6kalut ennen aloittamista. K\u00e4yt\u00e4mme Node.js 22 LTS -versiota, joka on aktiivinen LTS-julkaisu vuonna 2026 ja saa tietoturvap\u00e4ivityksi\u00e4 huhtikuuhun 2027 asti. LTS on oikea valinta tuotantoon, koska se on yritysymp\u00e4rist\u00f6jen vakio-oletus. Node.js 24 on uudempi Current-linja, mutta sille ei kannata rakentaa tuotantokriittist\u00e4 todennusta viel\u00e4.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Komponentti<\/th><th>Versio<\/th><th>Tarkoitus<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22 LTS (aktiivinen LTS 2026)<\/td><td>Ajoymp\u00e4rist\u00f6<\/td><\/tr><tr><td>Express<\/td><td>5.x<\/td><td>Web-kehys<\/td><\/tr><tr><td>express-session<\/td><td>1.18.x<\/td><td>Istuntov\u00e4liohjelmisto<\/td><\/tr><tr><td>connect-redis<\/td><td>8.x<\/td><td>Redis-istuntovarasto<\/td><\/tr><tr><td>redis (node-redis)<\/td><td>4.x tai uudempi<\/td><td>Redis-asiakas<\/td><\/tr><tr><td>bcrypt<\/td><td>uusin versio<\/td><td>Salasanojen tiivistys<\/td><\/tr><tr><td>helmet<\/td><td>8.x<\/td><td>HTTP-turvaotsakkeet<\/td><\/tr><tr><td>express-rate-limit<\/td><td>7.x<\/td><td>Kirjautumisen rajoitus<\/td><\/tr><tr><td>Redis-palvelin<\/td><td>7.x tai uudempi<\/td><td>Istuntovarasto (tuotanto)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Tarkista Node-versiosi komennolla <code>node --version<\/code>. Sen pit\u00e4isi alkaa numerolla <code>v22<\/code>. Jos sinulla on vanhempi versio, asenna nvm tai lataa LTS-asennuspaketti osoitteesta <a href=\"https:\/\/nodejs.org\/en\/about\/previous-releases\" target=\"_blank\" rel=\"noopener\">nodejs.org<\/a>. Redis-palvelimen voit ajaa paikallisesti Dockerilla tai asentaa suoraan; tuotannossa k\u00e4yt\u00e4 erillist\u00e4 Redis-instanssia. Varmista my\u00f6s, ett\u00e4 sovelluksesi tarjoillaan HTTPS:n yli, koska <code>Secure<\/code>-ev\u00e4ste vaatii TLS-yhteyden. Jos TLS ei ole viel\u00e4 hallussa, lue <a href=\"\/https-ja-tls\/\">HTTPS- ja TLS-oppaamme<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-1-projektin-alustus-ja-riippuvuudet\">Vaihe 1: Projektin alustus ja riippuvuudet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Luo uusi hakemisto ja alusta npm-projekti. K\u00e4yt\u00e4mme ES-moduuleja, joten lis\u00e4\u00e4mme <code>\"type\": \"module\"<\/code> package.json-tiedostoon. Asenna sen j\u00e4lkeen kaikki riippuvuudet yhdell\u00e4 komennolla. Pid\u00e4mme tuotanto- ja kehitysriippuvuudet erill\u00e4\u00e4n, jotta tuotantoasennus pysyy kevyen\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir turvalliset-sessiot && cd turvalliset-sessiot\nnpm init -y\nnpm pkg set type=\"module\"\n\n# Tuotantoriippuvuudet\nnpm install express express-session connect-redis redis bcrypt helmet express-rate-limit dotenv\n\n# Kehitysriippuvuus automaattiseen uudelleenkaynnistykseen\nnpm install --save-dev nodemon<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Komennon j\u00e4lkeen <code>package.json<\/code>-tiedostosi sis\u00e4lt\u00e4\u00e4 kaikki tarvittavat paketit. Lis\u00e4\u00e4 viel\u00e4 k\u00e4ynnistyskomennot. Avaa <code>package.json<\/code> ja varmista, ett\u00e4 <code>scripts<\/code>-osio n\u00e4ytt\u00e4\u00e4 seuraavalta. <code>dev<\/code>-komento k\u00e4ynnist\u00e4\u00e4 palvelimen uudelleen jokaisen tallennuksen j\u00e4lkeen, <code>start<\/code> taas on tarkoitettu tuotantoon.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"node app.js\",\n    \"dev\": \"nodemon app.js\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luo lopuksi <code>.env<\/code>-tiedosto ymp\u00e4rist\u00f6muuttujille ja lis\u00e4\u00e4 se heti <code>.gitignore<\/code>-tiedostoon. Istuntosalaisuus ei saa koskaan p\u00e4\u00e4ty\u00e4 versionhallintaan. Generoi vahva satunnainen salaisuus komennolla <code>node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"<\/code> ja liit\u00e4 tulos <code>.env<\/code>-tiedostoon avaimella <code>SESSION_SECRET<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .env\nNODE_ENV=development\nPORT=3000\nSESSION_SECRET=korvaa_tama_64_merkin_satunnaisella_hex_merkkijonolla\nREDIS_URL=redis:\/\/127.0.0.1:6379<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-2-express-palvelimen-perusrunko\">Vaihe 2: Express-palvelimen perusrunko<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Rakennetaan ensin minimaalinen Express-palvelin, jonka p\u00e4\u00e4lle kasaamme istunnonhallinnan. Luo tiedosto <code>app.js<\/code>. Lataamme ymp\u00e4rist\u00f6muuttujat dotenvilla heti alussa, lis\u00e4\u00e4mme rungon JSON- ja lomakedatan j\u00e4sent\u00e4miseen ja avaamme yhden testireitin. T\u00e4ss\u00e4 vaiheessa istuntoja ei viel\u00e4 ole, vaan varmistamme, ett\u00e4 palvelin k\u00e4ynnistyy puhtaasti.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js\nimport \"dotenv\/config\";\nimport express from \"express\";\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\napp.get(\"\/\", (req, res) =&gt; {\n  res.send(\"Palvelin toimii.\");\n});\n\napp.listen(PORT, () =&gt; {\n  console.log(`Palvelin kuuntelee portissa ${PORT}`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4ynnist\u00e4 palvelin komennolla <code>npm run dev<\/code>. Avaa selaimessa osoite <code>http:\/\/localhost:3000<\/code>, ja sinun pit\u00e4isi n\u00e4hd\u00e4 teksti &#8220;Palvelin toimii.&#8221; Terminaalissa n\u00e4kyy seuraava tuloste. Jos n\u00e4et sen, perusrunko on kunnossa ja voimme siirty\u00e4 istuntoihin.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm run dev\n[nodemon] starting `node app.js`\nPalvelin kuuntelee portissa 3000<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-3-express-session-valiohjelmiston-konfigurointi\">Vaihe 3: express-session-v\u00e4liohjelmiston konfigurointi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nyt lis\u00e4\u00e4mme istunnonhallinnan. <code>express-session<\/code> on virallinen v\u00e4liohjelmisto, joka luo ja hallinnoi <code>req.session<\/code>-olioita. Aluksi k\u00e4yt\u00e4mme oletusarvoista muistivarastoa, jotta n\u00e4emme nopeasti tuloksen, mutta korvaamme sen Redisill\u00e4 vaiheessa 5. Lis\u00e4\u00e4 seuraava koodi <code>app.js<\/code>-tiedostoon ennen reittej\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import session from \"express-session\";\n\nconst isProd = process.env.NODE_ENV === \"production\";\n\napp.use(\n  session({\n    name: \"sid\",                       \/\/ oletusnimi connect.sid kannattaa vaihtaa\n    secret: process.env.SESSION_SECRET,\n    resave: false,                     \/\/ ei tallenneta jos mikaan ei muuttunut\n    saveUninitialized: false,          \/\/ ei luoda istuntoa tyhjille vierailuille\n    cookie: {\n      httpOnly: true,\n      secure: isProd,                  \/\/ vaatii HTTPS:n tuotannossa\n      sameSite: \"lax\",\n      maxAge: 1000 * 60 * 60           \/\/ 1 tunti millisekunteina\n    }\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Kaksi asetusta ansaitsee erityishuomion. <code>resave: false<\/code> est\u00e4\u00e4 istunnon turhan tallentamisen jokaisella pyynn\u00f6ll\u00e4, mik\u00e4 v\u00e4hent\u00e4\u00e4 kuormaa ja kilpailutilanteita. <code>saveUninitialized: false<\/code> taas est\u00e4\u00e4 tyhjien istuntojen luomisen vierailijoille, jotka eiv\u00e4t ole viel\u00e4 kirjautuneet. T\u00e4m\u00e4 on t\u00e4rke\u00e4\u00e4 sek\u00e4 yksityisyyden ett\u00e4 GDPR-vaatimusten kannalta, koska et aseta ev\u00e4stett\u00e4 ennen kuin k\u00e4ytt\u00e4j\u00e4 todella tarvitsee istunnon. Lue lis\u00e4\u00e4 virallisesta <a href=\"https:\/\/expressjs.com\/en\/resources\/middleware\/session.html\" target=\"_blank\" rel=\"noopener\">express-session-dokumentaatiosta<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Testaa istunto lis\u00e4\u00e4m\u00e4ll\u00e4 laskurireitti. Lis\u00e4\u00e4 alla oleva koodi reittien sekaan ja p\u00e4ivit\u00e4 selainta useita kertoja. Laskurin pit\u00e4isi kasvaa yhdell\u00e4 joka p\u00e4ivityksell\u00e4, mik\u00e4 todistaa ett\u00e4 istunto s\u00e4ilyy pyynt\u00f6jen v\u00e4lill\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.get(\"\/laskuri\", (req, res) =&gt; {\n  req.session.kaynnit = (req.session.kaynnit || 0) + 1;\n  res.send(`Vierailuja tassa istunnossa: ${req.session.kaynnit}`);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-4-turvalliset-evasteasetukset\">Vaihe 4: Turvalliset ev\u00e4steasetukset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Istuntoev\u00e4steen asetukset ratkaisevat, kuinka hyvin se kest\u00e4\u00e4 yleisimpi\u00e4 hy\u00f6kk\u00e4yksi\u00e4. Kolme lippua ovat ehdottomia: <code>HttpOnly<\/code> est\u00e4\u00e4 JavaScripti\u00e4 lukemasta ev\u00e4stett\u00e4, mik\u00e4 rajoittaa XSS-hy\u00f6kk\u00e4yksen kyky\u00e4 varastaa istunto. <code>Secure<\/code> varmistaa, ettei ev\u00e4stett\u00e4 l\u00e4hetet\u00e4 koskaan salaamattoman HTTP-yhteyden yli. <code>SameSite<\/code> rajoittaa ev\u00e4steen l\u00e4hett\u00e4mist\u00e4 sivustojen v\u00e4lisiss\u00e4 pyynn\u00f6iss\u00e4 ja torjuu monia CSRF-tyyppisi\u00e4 hy\u00f6kk\u00e4yksi\u00e4. Alla oleva taulukko kokoaa attribuutit ja suositusarvot.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Attribuutti<\/th><th>Suositusarvo<\/th><th>Mit\u00e4 se est\u00e4\u00e4<\/th><\/tr><\/thead><tbody><tr><td>HttpOnly<\/td><td>true<\/td><td>Ev\u00e4steen varastamisen JavaScriptill\u00e4 (XSS)<\/td><\/tr><tr><td>Secure<\/td><td>true (tuotanto)<\/td><td>L\u00e4hetyksen salaamattoman HTTP:n yli<\/td><\/tr><tr><td>SameSite<\/td><td>lax tai strict<\/td><td>Sivustojenv\u00e4lisen pyynn\u00f6n (CSRF)<\/td><\/tr><tr><td>maxAge<\/td><td>lyhyt, esim. 1 h<\/td><td>Pitk\u00e4ik\u00e4isen istunnon v\u00e4\u00e4rink\u00e4yt\u00f6n<\/td><\/tr><tr><td>name<\/td><td>oma, ei connect.sid<\/td><td>Teknologiapinon paljastumisen<\/td><\/tr><tr><td>domain \/ path<\/td><td>mahdollisimman kapea<\/td><td>Ev\u00e4steen liiallisen levi\u00e4misen<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Huomaa erityispiirre <code>SameSite=None<\/code>-arvosta: sit\u00e4 saa k\u00e4ytt\u00e4\u00e4 vain yhdess\u00e4 <code>Secure<\/code>-lipun kanssa, koska selaimet hylk\u00e4\u00e4v\u00e4t vuonna 2026 salaamattoman sivustojenv\u00e4lisen ev\u00e4steen kokonaan. Useimmissa kirjautumissovelluksissa <code>lax<\/code> on oikea valinta, koska se sallii normaalin navigoinnin mutta est\u00e4\u00e4 vaaralliset POST-pohjaiset sivustojenv\u00e4liset pyynn\u00f6t. K\u00e4yt\u00e4 <code>strict<\/code>-arvoa, jos sovellus ei tarvitse ulkoisia linkkej\u00e4 sis\u00e4\u00e4nkirjautuneeseen tilaan. Voit tarkistaa ev\u00e4steen attribuutit selaimen kehitysty\u00f6kaluista v\u00e4lilehdelt\u00e4 Application tai Storage, tai katsoa <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Set-Cookie\" target=\"_blank\" rel=\"noopener\">MDN:n Set-Cookie-dokumentaatiosta<\/a> tarkat m\u00e4\u00e4ritelm\u00e4t.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ev\u00e4steasetukset eiv\u00e4t yksin riit\u00e4, jos sovelluksessa on XSS-aukko. <code>HttpOnly<\/code> pienent\u00e4\u00e4 vahinkoa, mutta paras suoja on est\u00e4\u00e4 XSS kokonaan sy\u00f6tteiden validoinnilla ja tulostuksen koodauksella. Lis\u00e4\u00e4mme vaiheessa 9 my\u00f6s Helmetin, joka asettaa sis\u00e4lt\u00f6turvak\u00e4yt\u00e4nn\u00f6n ja muut suojaotsakkeet. Yhdess\u00e4 n\u00e4m\u00e4 muodostavat kerroksellisen puolustuksen, jossa yksitt\u00e4isen kerroksen pett\u00e4minen ei viel\u00e4 avaa istuntoa hy\u00f6kk\u00e4\u00e4j\u00e4lle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-5-redis-istuntovarasto-tuotantoon\">Vaihe 5: Redis-istuntovarasto tuotantoon<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Oletusvarasto eli MemoryStore on tarkoitettu vain kehitykseen. Se vuotaa muistia, ei skaalaudu usealle palvelininstanssille ja tyhjenee jokaisella uudelleenk\u00e4ynnistyksell\u00e4, jolloin kaikki k\u00e4ytt\u00e4j\u00e4t kirjautuvat ulos. Tuotannossa istunnot kuuluvat jaettuun varastoon, ja Redis on t\u00e4h\u00e4n vakiintunut valinta. Se on nopea, kest\u00e4\u00e4 useita instansseja ja osaa vanhentaa avaimet automaattisesti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4yt\u00e4mme <code>connect-redis<\/code>-pakettia version 8 mukaisella nimetyll\u00e4 exportilla ja <code>redis<\/code>-asiakasta (node-redis). Korvaa edellisen vaiheen <code>session()<\/code>-konfiguraatio seuraavalla. Yhdist\u00e4 Redis-asiakas ennen kuin annat sen varastolle, ja anna avaimille selke\u00e4 etuliite, jotta istunnot erottuvat muusta Redis-datasta.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createClient } from \"redis\";\nimport { RedisStore } from \"connect-redis\";\n\n\/\/ Luo ja yhdista Redis-asiakas\nconst redisClient = createClient({ url: process.env.REDIS_URL });\nredisClient.on(\"error\", (err) =&gt; console.error(\"Redis-virhe:\", err));\nawait redisClient.connect();\n\nconst store = new RedisStore({\n  client: redisClient,\n  prefix: \"sess:\",\n  ttl: 60 * 60            \/\/ sekunteina, vastaa evasteen maxAgea\n});\n\napp.use(\n  session({\n    store,\n    name: \"sid\",\n    secret: process.env.SESSION_SECRET,\n    resave: false,\n    saveUninitialized: false,\n    cookie: {\n      httpOnly: true,\n      secure: isProd,\n      sameSite: \"lax\",\n      maxAge: 1000 * 60 * 60\n    }\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Voit varmistaa, ett\u00e4 istunnot todella tallentuvat Redisiin, komentorivin <code>redis-cli<\/code>-ty\u00f6kalulla. Kirjaudu sovellukseen tai k\u00e4yt\u00e4 laskurireitti\u00e4 kerran, ja listaa sitten avaimet. N\u00e4et istunto-ID:t etuliitteell\u00e4 <code>sess:<\/code>. Tuotannossa varmista, ettei Redis ole avoimessa internetiss\u00e4 ilman salasanaa, ja k\u00e4yt\u00e4 TLS-yhteytt\u00e4 Redikseen, jos se sijaitsee eri koneella. Lis\u00e4tietoa asiakaskirjastosta l\u00f6ytyy <a href=\"https:\/\/redis.io\/docs\/latest\/develop\/clients\/nodejs\/\" target=\"_blank\" rel=\"noopener\">Redisin virallisesta node-redis-dokumentaatiosta<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ redis-cli\n127.0.0.1:6379&gt; KEYS sess:*\n1) \"sess:Hk3m9pQ2vLxR8tYw1nZ\"\n127.0.0.1:6379&gt; TTL sess:Hk3m9pQ2vLxR8tYw1nZ\n(integer) 3587<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-6-salasanojen-tiivistys-ja-kirjautuminen\">Vaihe 6: Salasanojen tiivistys ja kirjautuminen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Istunto syntyy onnistuneen kirjautumisen j\u00e4lkeen, joten tarvitsemme kirjautumisreitin. Salasanoja ei koskaan tallenneta selkokielisin\u00e4 eik\u00e4 salata palautettavalla menetelm\u00e4ll\u00e4, vaan ne tiivistet\u00e4\u00e4n yksisuuntaisella algoritmilla. K\u00e4yt\u00e4mme t\u00e4ss\u00e4 bcrypti\u00e4 kustannuskertoimella 12. bcrypt on hyv\u00e4 oletus, mutta viel\u00e4 vahvempi nykyvalinta on Argon2; vertailua salasanojen suojaamisesta l\u00f6yd\u00e4t <a href=\"\/salasanaturvallisuus\/\">salasanaturvallisuusoppaastamme<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Alla oleva esimerkki k\u00e4ytt\u00e4\u00e4 muistissa olevaa demok\u00e4ytt\u00e4j\u00e4\u00e4 selkeyden vuoksi. Oikeassa sovelluksessa hakisit k\u00e4ytt\u00e4j\u00e4n tietokannasta. T\u00e4rkein turvallisuusyksityiskohta on, ett\u00e4 vertaamme sy\u00f6tetty\u00e4 salasanaa tiivisteeseen <code>bcrypt.compare<\/code>-funktiolla, emmek\u00e4 koskaan palauta tietoa siit\u00e4, oliko vika k\u00e4ytt\u00e4j\u00e4tunnuksessa vai salasanassa. T\u00e4m\u00e4 est\u00e4\u00e4 k\u00e4ytt\u00e4j\u00e4tunnusten kalastelun virheilmoitusten avulla.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import bcrypt from \"bcrypt\";\n\n\/\/ Demokayttaja: salasanan \"salasana123\" bcrypt-tiiviste\nconst demoUser = {\n  id: 1,\n  username: \"matti\",\n  passwordHash: await bcrypt.hash(\"salasana123\", 12)\n};\n\napp.post(\"\/kirjaudu\", async (req, res, next) =&gt; {\n  const { username, password } = req.body;\n\n  const user = username === demoUser.username ? demoUser : null;\n  const ok = user &amp;&amp; (await bcrypt.compare(password, user.passwordHash));\n\n  if (!ok) {\n    \/\/ Sama viesti molemmissa tapauksissa\n    return res.status(401).send(\"Virheellinen kayttajatunnus tai salasana.\");\n  }\n\n  \/\/ Istunnon uudistus tehdaan seuraavassa vaiheessa\n  req.session.userId = user.id;\n  res.send(\"Kirjautuminen onnistui.\");\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4m\u00e4 versio toimii, mutta siit\u00e4 puuttuu yksi kriittinen suoja: istunto-ID:t\u00e4 ei uudisteta kirjautumisen yhteydess\u00e4. Korjaamme t\u00e4m\u00e4n heti seuraavassa vaiheessa. Suojaa kirjautumisreitti my\u00f6s selauspohjaista CSRF:\u00e4\u00e4 vastaan, jos k\u00e4yt\u00e4t lomakkeita; siihen sopii esimerkiksi erillinen CSRF-token, kuten <a href=\"\/csrf-protection-nodejs\/\">CSRF-suojausoppaassamme<\/a> kuvataan.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-7-istunto-idn-uudistus-session-fixation-suojaksi\">Vaihe 7: Istunto-ID:n uudistus session fixation -suojaksi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Session fixation on hy\u00f6kk\u00e4ys, jossa hy\u00f6kk\u00e4\u00e4j\u00e4 asettaa uhrille ennalta tunnetun istunto-ID:n ennen kirjautumista. Kun uhri kirjautuu samalla ID:ll\u00e4, hy\u00f6kk\u00e4\u00e4j\u00e4 p\u00e4\u00e4see k\u00e4siksi todennettuun istuntoon, koska h\u00e4n tiet\u00e4\u00e4 tunnisteen valmiiksi. Suoja on yksinkertainen ja pakollinen: luo istunnolle uusi ID heti onnistuneen kirjautumisen j\u00e4lkeen funktiolla <code>req.session.regenerate<\/code>. Vanha tunniste mit\u00e4t\u00f6ityy, eik\u00e4 hy\u00f6kk\u00e4\u00e4j\u00e4n ennalta asettama ID en\u00e4\u00e4 kelpaa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">P\u00e4ivit\u00e4 kirjautumisreitti niin, ett\u00e4 se uudistaa istunnon ja tallentaa k\u00e4ytt\u00e4j\u00e4n ID:n vasta uudistuksen j\u00e4lkeen. Kutsu lopuksi <code>req.session.save<\/code>, jotta istunto kirjoitetaan varastoon ennen vastausta. T\u00e4m\u00e4 on OWASP:n suosittelema malli, joka kuvataan tarkemmin <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Session_Management_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">OWASP Session Management Cheat Sheetiss\u00e4<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.post(\"\/kirjaudu\", async (req, res, next) =&gt; {\n  const { username, password } = req.body;\n  const user = username === demoUser.username ? demoUser : null;\n  const ok = user &amp;&amp; (await bcrypt.compare(password, user.passwordHash));\n\n  if (!ok) {\n    return res.status(401).send(\"Virheellinen kayttajatunnus tai salasana.\");\n  }\n\n  \/\/ Esta session fixation: luo uusi istunto-ID\n  req.session.regenerate((err) =&gt; {\n    if (err) return next(err);\n\n    req.session.userId = user.id;\n    req.session.kirjautumisaika = Date.now();\n\n    req.session.save((err) =&gt; {\n      if (err) return next(err);\n      res.send(\"Kirjautuminen onnistui.\");\n    });\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Suojaa nyt my\u00f6s sovelluksen sis\u00e4iset reitit. Kirjoita pieni v\u00e4liohjelmisto, joka tarkistaa, ett\u00e4 <code>req.session.userId<\/code> on olemassa, ja ohjaa muuten kirjautumissivulle. T\u00e4ll\u00e4 tavalla et toista tarkistusta jokaisessa reitiss\u00e4 erikseen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function vaadiKirjautuminen(req, res, next) {\n  if (!req.session.userId) {\n    return res.status(401).send(\"Kirjaudu ensin sisaan.\");\n  }\n  next();\n}\n\napp.get(\"\/hallinta\", vaadiKirjautuminen, (req, res) =&gt; {\n  res.send(`Tervetuloa, kayttaja ${req.session.userId}.`);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-8-uloskirjautuminen-ja-istunnon-tuhoaminen\">Vaihe 8: Uloskirjautuminen ja istunnon tuhoaminen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Uloskirjautumisen pit\u00e4\u00e4 tehd\u00e4 kaksi asiaa: poistaa istunto palvelimen varastosta ja tyhjent\u00e4\u00e4 ev\u00e4ste selaimesta. Pelkk\u00e4 ev\u00e4steen poisto ei riit\u00e4, koska istunto j\u00e4isi el\u00e4m\u00e4\u00e4n Redikseen ja varastettu ev\u00e4ste toimisi yh\u00e4. K\u00e4yt\u00e4 <code>req.session.destroy<\/code>-funktiota, joka poistaa istunnon varastosta, ja tyhjenn\u00e4 sen j\u00e4lkeen ev\u00e4ste <code>res.clearCookie<\/code>-kutsulla samalla nimell\u00e4, jonka asetit konfiguraatiossa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.post(\"\/kirjaudu-ulos\", (req, res, next) =&gt; {\n  req.session.destroy((err) =&gt; {\n    if (err) return next(err);\n    res.clearCookie(\"sid\");          \/\/ sama nimi kuin session-konfiguraatiossa\n    res.send(\"Olet kirjautunut ulos.\");\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Varmista uloskirjautuminen tarkistamalla Redisist\u00e4, ett\u00e4 istunto on todella poistunut. Kirjaudu sis\u00e4\u00e4n, listaa avaimet <code>redis-cli<\/code>:ll\u00e4, kirjaudu ulos ja listaa uudelleen. Avaimen pit\u00e4isi kadota. Jos toteutat istunnon vanhenemisen aktiivisuuden mukaan, harkitse my\u00f6s niin sanottua liukuvaa vanhenemista, jossa <code>maxAge<\/code> nollataan jokaisella pyynn\u00f6ll\u00e4 asettamalla <code>rolling: true<\/code> session-konfiguraatioon. T\u00e4ll\u00f6in aktiivinen k\u00e4ytt\u00e4j\u00e4 pysyy kirjautuneena mutta passiivinen istunto vanhenee turvallisesti.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-9-reverse-proxy-trust-proxy-ja-https\">Vaihe 9: Reverse proxy, trust proxy ja HTTPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tuotannossa Node-sovellus on l\u00e4hes aina k\u00e4\u00e4nteisproxyn, kuten Nginxin, kuormantasaajan tai pilvi-ingressin, takana. Proxy purkaa TLS:n ja v\u00e4litt\u00e4\u00e4 pyynn\u00f6n sovellukselle tavallisena HTTP:n\u00e4. T\u00e4m\u00e4 aiheuttaa ongelman: Express luulee yhteytt\u00e4 turvattomaksi, jolloin <code>Secure<\/code>-ev\u00e4ste ei l\u00e4hde lainkaan ja kirjautuminen rikkoutuu hiljaisesti. Ratkaisu on kertoa Expressille, ett\u00e4 se on luotetun proxyn takana, asetuksella <code>trust proxy<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lis\u00e4\u00e4 my\u00f6s Helmet, joka asettaa joukon HTTP-turvaotsakkeita, kuten sis\u00e4lt\u00f6turvak\u00e4yt\u00e4nn\u00f6n ja HSTS:n. Yhdess\u00e4 n\u00e4m\u00e4 muodostavat tuotantokelpoisen turvaperustan. Lis\u00e4\u00e4 seuraava koodi <code>app.js<\/code>-tiedoston alkuun, heti Express-instanssin luomisen j\u00e4lkeen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import helmet from \"helmet\";\n\napp.use(helmet());\n\nif (isProd) {\n  \/\/ Luota yhteen edessa olevaan proxyyn (esim. Nginx)\n  app.set(\"trust proxy\", 1);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Kun <code>trust proxy<\/code> on asetettu, Express lukee <code>X-Forwarded-Proto<\/code>-otsakkeen ja tunnistaa alkuper\u00e4isen yhteyden HTTPS:ksi. Varmista, ett\u00e4 proxysi todella asettaa t\u00e4m\u00e4n otsakkeen. Nginxiss\u00e4 se tehd\u00e4\u00e4n rivill\u00e4 <code>proxy_set_header X-Forwarded-Proto $scheme;<\/code>. \u00c4l\u00e4 koskaan aseta <code>trust proxy<\/code> arvoon <code>true<\/code> ilman ymm\u00e4rryst\u00e4 verkkorakenteesta, koska se saa Expressin luottamaan kenen tahansa l\u00e4hett\u00e4m\u00e4\u00e4n forwarded-otsakkeeseen, mik\u00e4 voi mahdollistaa IP-osoitteen v\u00e4\u00e4rent\u00e4misen. Jos pystyt\u00e4t oman TLS-p\u00e4\u00e4tt\u00e4v\u00e4n proxyn, <a href=\"\/wireguard-vpn-palvelin-linux\/\">palvelinopastuksemme<\/a> ja <a href=\"\/https-ja-tls\/\">HTTPS-oppaamme<\/a> auttavat alkuun.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-10-kirjautumisen-rate-limiting-ja-brute-force-suoja\">Vaihe 10: Kirjautumisen rate limiting ja brute force -suoja<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vahvinkaan istunnonhallinta ei auta, jos hy\u00f6kk\u00e4\u00e4j\u00e4 voi arvata salasanan rajattomalla m\u00e4\u00e4r\u00e4ll\u00e4 yrityksi\u00e4. Siksi kirjautumisreitti on rajoitettava. <code>express-rate-limit<\/code> tarjoaa yksinkertaisen tavan rajata pyynt\u00f6jen m\u00e4\u00e4r\u00e4\u00e4 IP-osoitetta kohden. Asetamme tiukan rajan kirjautumiselle: enint\u00e4\u00e4n viisi yrityst\u00e4 15 minuutissa. T\u00e4m\u00e4 hidastaa brute force -hy\u00f6kk\u00e4yst\u00e4 merkitt\u00e4v\u00e4sti rikkomatta tavallista k\u00e4ytt\u00f6\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { rateLimit } from \"express-rate-limit\";\n\nconst kirjautumisRajoitin = rateLimit({\n  windowMs: 15 * 60 * 1000,   \/\/ 15 minuuttia\n  max: 5,                     \/\/ enintaan 5 yritysta per IP\n  standardHeaders: true,\n  legacyHeaders: false,\n  message: \"Liikaa kirjautumisyrityksia. Yrita uudelleen myohemmin.\"\n});\n\n\/\/ Liita rajoitin vain kirjautumisreittiin\napp.post(\"\/kirjaudu\", kirjautumisRajoitin, async (req, res, next) =&gt; {\n  \/\/ ... vaiheen 7 kirjautumislogiikka\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tuotannossa, jossa on useita instansseja, IP-pohjainen laskuri kannattaa tallentaa Redikseen samaan tapaan kuin istunnot, jotta raja toimii yhten\u00e4isesti kaikilla palvelimilla. T\u00e4h\u00e4n on valmis <code>rate-limit-redis<\/code>-varasto. Harkitse my\u00f6s t\u00e4ydent\u00e4vi\u00e4 suojia: viivett\u00e4 ep\u00e4onnistuneiden yritysten j\u00e4lkeen, CAPTCHA:a toistuvien ep\u00e4onnistumisten kohdalla ja kaksivaiheista tunnistautumista. Kaksivaiheinen tunnistautuminen nostaa suojan kokonaan uudelle tasolle, ja vertailimme menetelmi\u00e4 <a href=\"\/kaksivaiheinen-tunnistautuminen-vertailu\/\">2FA-vertailussamme<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"taydellinen-toimiva-esimerkkiprojekti\">T\u00e4ydellinen toimiva esimerkkiprojekti<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4ss\u00e4 on koko <code>app.js<\/code> koottuna yhteen. T\u00e4m\u00e4 on toimiva runko, jonka voit k\u00e4ynnist\u00e4\u00e4 heti, kun Redis on k\u00e4ynniss\u00e4 ja <code>.env<\/code> on t\u00e4ytetty. Korvaa demok\u00e4ytt\u00e4j\u00e4 omalla tietokantahaullasi tuotantoa varten. Kaikki vaiheiden 1-10 suojat ovat mukana: turvalliset ev\u00e4steet, Redis-varasto, salasanan tiivistys, istunnon uudistus, uloskirjautuminen, trust proxy, Helmet ja kirjautumisen rajoitus.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js\nimport \"dotenv\/config\";\nimport express from \"express\";\nimport session from \"express-session\";\nimport { createClient } from \"redis\";\nimport { RedisStore } from \"connect-redis\";\nimport bcrypt from \"bcrypt\";\nimport helmet from \"helmet\";\nimport { rateLimit } from \"express-rate-limit\";\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\nconst isProd = process.env.NODE_ENV === \"production\";\n\napp.use(helmet());\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\nif (isProd) app.set(\"trust proxy\", 1);\n\n\/\/ Redis-asiakas ja istuntovarasto\nconst redisClient = createClient({ url: process.env.REDIS_URL });\nredisClient.on(\"error\", (err) =&gt; console.error(\"Redis-virhe:\", err));\nawait redisClient.connect();\n\nconst store = new RedisStore({ client: redisClient, prefix: \"sess:\", ttl: 3600 });\n\napp.use(\n  session({\n    store,\n    name: \"sid\",\n    secret: process.env.SESSION_SECRET,\n    resave: false,\n    saveUninitialized: false,\n    rolling: true,\n    cookie: {\n      httpOnly: true,\n      secure: isProd,\n      sameSite: \"lax\",\n      maxAge: 1000 * 60 * 60\n    }\n  })\n);\n\n\/\/ Demokayttaja\nconst demoUser = {\n  id: 1,\n  username: \"matti\",\n  passwordHash: await bcrypt.hash(\"salasana123\", 12)\n};\n\nconst kirjautumisRajoitin = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  standardHeaders: true,\n  legacyHeaders: false,\n  message: \"Liikaa kirjautumisyrityksia. Yrita uudelleen myohemmin.\"\n});\n\nfunction vaadiKirjautuminen(req, res, next) {\n  if (!req.session.userId) return res.status(401).send(\"Kirjaudu ensin sisaan.\");\n  next();\n}\n\napp.post(\"\/kirjaudu\", kirjautumisRajoitin, async (req, res, next) =&gt; {\n  const { username, password } = req.body;\n  const user = username === demoUser.username ? demoUser : null;\n  const ok = user &amp;&amp; (await bcrypt.compare(password, user.passwordHash));\n  if (!ok) return res.status(401).send(\"Virheellinen kayttajatunnus tai salasana.\");\n\n  req.session.regenerate((err) =&gt; {\n    if (err) return next(err);\n    req.session.userId = user.id;\n    req.session.kirjautumisaika = Date.now();\n    req.session.save((err) =&gt; {\n      if (err) return next(err);\n      res.send(\"Kirjautuminen onnistui.\");\n    });\n  });\n});\n\napp.post(\"\/kirjaudu-ulos\", (req, res, next) =&gt; {\n  req.session.destroy((err) =&gt; {\n    if (err) return next(err);\n    res.clearCookie(\"sid\");\n    res.send(\"Olet kirjautunut ulos.\");\n  });\n});\n\napp.get(\"\/hallinta\", vaadiKirjautuminen, (req, res) =&gt; {\n  res.send(`Tervetuloa, kayttaja ${req.session.userId}.`);\n});\n\napp.listen(PORT, () =&gt; console.log(`Palvelin kuuntelee portissa ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Testaa kokonaisuus curlilla. Kirjaudu sis\u00e4\u00e4n ja tallenna ev\u00e4ste tiedostoon, hae sitten suojattu reitti samalla ev\u00e4steell\u00e4 ja kirjaudu lopuksi ulos. Alla n\u00e4kyy odotettu kulku ja tuloste. Huomaa, ett\u00e4 ilman ev\u00e4stett\u00e4 suojattu reitti palauttaa virheen 401, mik\u00e4 todistaa ett\u00e4 suojaus toimii.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -c evasteet.txt -X POST http:\/\/localhost:3000\/kirjaudu \\\n    -d \"username=matti&amp;password=salasana123\"\nKirjautuminen onnistui.\n\n$ curl -b evasteet.txt http:\/\/localhost:3000\/hallinta\nTervetuloa, kayttaja 1.\n\n$ curl http:\/\/localhost:3000\/hallinta\nKirjaudu ensin sisaan.\n\n$ curl -b evasteet.txt -X POST http:\/\/localhost:3000\/kirjaudu-ulos\nOlet kirjautunut ulos.<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"yleiset-sudenkuopat-ja-miten-valtat-ne\">Yleiset sudenkuopat ja miten v\u00e4lt\u00e4t ne<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Istunnonhallinnan virheet toistuvat projektista toiseen. T\u00e4ss\u00e4 viisi yleisint\u00e4 sudenkuoppaa ja niiden korjaus. Jokainen n\u00e4ist\u00e4 on aiheuttanut todellisia tietoturvaongelmia tuotannossa, joten k\u00e4y lista huolella l\u00e4pi ennen julkaisua.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>MemoryStore tuotannossa.<\/strong> Oletusvarasto vuotaa muistia ja katoaa uudelleenk\u00e4ynnistyksess\u00e4. Express varoittaa t\u00e4st\u00e4 konsolissa. K\u00e4yt\u00e4 aina Redist\u00e4 tai muuta jaettua varastoa tuotannossa, kuten vaiheessa 5.<\/li><li><strong>Istunto-ID:t\u00e4 ei uudisteta kirjautuessa.<\/strong> T\u00e4m\u00e4 j\u00e4tt\u00e4\u00e4 oven auki session fixation -hy\u00f6kk\u00e4ykselle. Kutsu aina <code>req.session.regenerate<\/code> onnistuneen kirjautumisen j\u00e4lkeen, kuten vaiheessa 7.<\/li><li><strong>Secure-ev\u00e4ste ilman trust proxya.<\/strong> Proxyn takana <code>secure: true<\/code> est\u00e4\u00e4 ev\u00e4steen l\u00e4hetyksen kokonaan, ja kirjautuminen &#8220;ei toimi&#8221; ilman virhett\u00e4. Aseta <code>trust proxy<\/code>, kuten vaiheessa 9.<\/li><li><strong>Salaisuus kovakoodattuna tai versionhallinnassa.<\/strong> Jos <code>SESSION_SECRET<\/code> vuotaa, hy\u00f6kk\u00e4\u00e4j\u00e4 voi v\u00e4\u00e4rent\u00e4\u00e4 ev\u00e4steen allekirjoituksen. Pid\u00e4 se aina ymp\u00e4rist\u00f6muuttujassa ja <code>.gitignore<\/code>-listalla.<\/li><li><strong>Uloskirjautuminen poistaa vain ev\u00e4steen.<\/strong> Jos et kutsu <code>req.session.destroy<\/code>, istunto j\u00e4\u00e4 el\u00e4m\u00e4\u00e4n varastoon ja varastettu ev\u00e4ste toimii yh\u00e4. Tuhoa aina istunto palvelimelta, kuten vaiheessa 8.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kuudes, usein unohtuva kohta koskee salasanan vaihtoa. Kun k\u00e4ytt\u00e4j\u00e4 vaihtaa salasanan tai havaitset ep\u00e4ilytt\u00e4v\u00e4\u00e4 toimintaa, mit\u00e4t\u00f6i kaikki kyseisen k\u00e4ytt\u00e4j\u00e4n aktiiviset istunnot. Palvelinpohjaisessa mallissa t\u00e4m\u00e4 on helppoa: pid\u00e4 Redisiss\u00e4 kirjaa k\u00e4ytt\u00e4j\u00e4n istunnoista ja poista ne kerralla. T\u00e4m\u00e4 on yksi t\u00e4rkeimmist\u00e4 syist\u00e4 valita palvelinpohjainen sessio tilattoman tokenin sijaan turvakriittisiss\u00e4 sovelluksissa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vianmaaritys-8-yleista-ongelmaa\">Vianm\u00e4\u00e4ritys: 8 yleist\u00e4 ongelmaa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kun istunnot eiv\u00e4t k\u00e4ytt\u00e4ydy odotetusti, ongelma on l\u00e4hes aina yhdess\u00e4 alla olevista kohdista. K\u00e4yt\u00e4 taulukkoa tarkistuslistana. Useimmat virheet liittyv\u00e4t ev\u00e4steen asetuksiin, proxyyn tai Redis-yhteyteen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Oire<\/th><th>Todenn\u00e4k\u00f6inen syy<\/th><th>Ratkaisu<\/th><\/tr><\/thead><tbody><tr><td>Istunto nollautuu joka pyynn\u00f6ll\u00e4<\/td><td>Ev\u00e4ste ei tallennu selaimeen<\/td><td>Tarkista secure-lippu ja trust proxy<\/td><\/tr><tr><td>Kirjautuminen toimii localhostissa mutta ei tuotannossa<\/td><td>secure: true ilman HTTPS:\u00e4\u00e4 tai proxya<\/td><td>Aseta trust proxy ja varmista TLS<\/td><\/tr><tr><td>&#8220;connect ECONNREFUSED&#8221; k\u00e4ynnistyksess\u00e4<\/td><td>Redis ei ole k\u00e4ynniss\u00e4 tai v\u00e4\u00e4r\u00e4 URL<\/td><td>K\u00e4ynnist\u00e4 Redis, tarkista REDIS_URL<\/td><\/tr><tr><td>Kaikki kirjautuvat ulos uudelleenk\u00e4ynnistyksess\u00e4<\/td><td>MemoryStore k\u00e4yt\u00f6ss\u00e4<\/td><td>Vaihda Redis-varastoon<\/td><\/tr><tr><td>Ev\u00e4ste n\u00e4kyy mutta req.session on tyhj\u00e4<\/td><td>SESSION_SECRET vaihtui<\/td><td>Pid\u00e4 salaisuus vakiona, k\u00e4yt\u00e4 rotaatiolistaa<\/td><\/tr><tr><td>SameSite-varoitus selaimen konsolissa<\/td><td>SameSite=None ilman Secure-lippua<\/td><td>Lis\u00e4\u00e4 secure: true tai vaihda lax-arvoon<\/td><\/tr><tr><td>Istunto ei vanhene koskaan<\/td><td>maxAge tai ttl puuttuu<\/td><td>Aseta cookie.maxAge ja varaston ttl<\/td><\/tr><tr><td>RedisStore is not a constructor -virhe<\/td><td>connect-redis-version v\u00e4\u00e4r\u00e4 import<\/td><td>K\u00e4yt\u00e4 nimetty\u00e4 exportia versiossa 8<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Viimeinen rivi on erityisen yleinen p\u00e4ivityksen j\u00e4lkeen. connect-redis vaihtoi version 8 my\u00f6t\u00e4 oletusexportin nimettyyn exporttiin, joten vanha <code>import RedisStore from \"connect-redis\"<\/code> ei en\u00e4\u00e4 toimi. Oikea muoto on <code>import { RedisStore } from \"connect-redis\"<\/code>. Jos n\u00e4et virheen &#8220;RedisStore is not a constructor&#8221;, t\u00e4m\u00e4 on l\u00e4hes varma syy. Tarkista my\u00f6s, ett\u00e4 <code>redisClient.connect()<\/code> on kutsuttu ja odotettu ennen varaston luomista, sill\u00e4 yhdist\u00e4m\u00e4t\u00f6n asiakas aiheuttaa hiljaisia tallennusvirheit\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"edistyneet-vinkit-kovaan-tuotantokayttoon\">Edistyneet vinkit kovaan tuotantok\u00e4ytt\u00f6\u00f6n<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"salaisuuden-rotaatio-ilman-uloskirjautumisia\">Salaisuuden rotaatio ilman uloskirjautumisia<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">express-session hyv\u00e4ksyy <code>secret<\/code>-kent\u00e4ksi my\u00f6s taulukon. Ensimm\u00e4ist\u00e4 salaisuutta k\u00e4ytet\u00e4\u00e4n uusien ev\u00e4steiden allekirjoittamiseen, mutta kaikkia listan salaisuuksia kelpuutetaan vanhojen ev\u00e4steiden tarkistamisessa. N\u00e4in voit vaihtaa allekirjoitussalaisuuden ilman, ett\u00e4 kaikki k\u00e4ytt\u00e4j\u00e4t kirjautuvat ulos. Lis\u00e4\u00e4 uusi salaisuus listan alkuun ja poista vanhin vasta, kun kaikki vanhat istunnot ovat vanhentuneet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>session({\n  secret: [process.env.SESSION_SECRET_NEW, process.env.SESSION_SECRET_OLD],\n  \/\/ ... muut asetukset\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"argon2-bcryptin-tilalle\">Argon2 bcryptin tilalle<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">OWASP suosittelee nyky\u00e4\u00e4n Argon2id-algoritmia salasanojen tiivist\u00e4miseen, koska se kest\u00e4\u00e4 paremmin GPU-pohjaisia hy\u00f6kk\u00e4yksi\u00e4. Vaihto on suoraviivainen: korvaa <code>bcrypt.hash<\/code> ja <code>bcrypt.compare<\/code> Argon2-vastineilla. S\u00e4ilyt\u00e4 taaksep\u00e4in yhteensopivuus tunnistamalla tiivisteen muoto ja tiivist\u00e4m\u00e4ll\u00e4 salasana uudelleen Argon2:lla seuraavan onnistuneen kirjautumisen yhteydess\u00e4. T\u00e4m\u00e4 mahdollistaa asteittaisen siirtym\u00e4n ilman pakotettua salasananvaihtoa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"istuntojen-sitominen-laitteeseen\">Istuntojen sitominen laitteeseen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Voit tallentaa istuntoon kevyit\u00e4 sormenj\u00e4lki\u00e4, kuten User-Agent-otsakkeen tai osittaisen IP-osoitteen, ja hyl\u00e4t\u00e4 istunnon, jos ne muuttuvat radikaalisti. T\u00e4m\u00e4 vaikeuttaa varastetun ev\u00e4steen k\u00e4ytt\u00f6\u00e4 toisesta ymp\u00e4rist\u00f6st\u00e4. \u00c4l\u00e4 kuitenkaan sido istuntoa t\u00e4ydelliseen IP-osoitteeseen, koska mobiiliverkoissa ja yritysten NAT:n takana IP vaihtuu jatkuvasti, ja sidonta kirjaisi k\u00e4ytt\u00e4j\u00e4t turhaan ulos. Tasapaino on t\u00e4rke\u00e4: liian tiukka sidonta heikent\u00e4\u00e4 k\u00e4ytett\u00e4vyytt\u00e4, liian l\u00f6ys\u00e4 ei suojaa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"istunnon-turvallisuuden-tarkistuslista-ennen-julkaisua\">Istunnon turvallisuuden tarkistuslista ennen julkaisua<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ennen kuin viet sovelluksen tuotantoon, k\u00e4y l\u00e4pi t\u00e4m\u00e4 tiivistetty tarkistuslista. Se kokoaa oppaan t\u00e4rkeimm\u00e4t turvatoimet yhteen paikkaan. Jos jokin kohta j\u00e4\u00e4 tyhj\u00e4ksi, palaa vastaavaan vaiheeseen ennen julkaisua. Turvallinen istunnonhallinta on kerroksellista, eik\u00e4 yksitt\u00e4inen kerros riit\u00e4 yksin.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Ev\u00e4ste k\u00e4ytt\u00e4\u00e4 <code>HttpOnly<\/code>-, <code>Secure<\/code>&#8211; ja <code>SameSite<\/code>-lippuja.<\/li><li>Ev\u00e4steen nimi on vaihdettu pois oletuksesta <code>connect.sid<\/code>.<\/li><li>Istunto-ID uudistetaan kirjautumisen yhteydess\u00e4.<\/li><li>Istuntovarasto on Redis, ei MemoryStore.<\/li><li>SESSION_SECRET on vahva, ymp\u00e4rist\u00f6muuttujassa ja versionhallinnan ulkopuolella.<\/li><li>Uloskirjautuminen kutsuu <code>req.session.destroy<\/code>.<\/li><li><code>trust proxy<\/code> on asetettu oikein proxyn takana.<\/li><li>Kirjautumisreitti on rate-limitattu.<\/li><li>Salasanat on tiivistetty bcryptill\u00e4 tai Argon2:lla.<\/li><li>maxAge ja varaston ttl on asetettu, jotta istunnot vanhenevat.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kun jokainen kohta on kunnossa, sovelluksesi istunnonhallinta t\u00e4ytt\u00e4\u00e4 OWASP:n keskeiset suositukset ja kest\u00e4\u00e4 yleisimm\u00e4t hy\u00f6kk\u00e4ykset. Muista, ett\u00e4 turvallisuus on jatkuva prosessi: seuraa riippuvuuksien tietoturvap\u00e4ivityksi\u00e4 ja p\u00e4ivit\u00e4 paketit s\u00e4\u00e4nn\u00f6llisesti. Yksikin haavoittuva riippuvuus voi mit\u00e4t\u00f6id\u00e4 huolellisen istunnonhallinnan. Lue my\u00f6s, miten <a href=\"\/tietomurrot\/\">tietomurrot<\/a> tyypillisesti etenev\u00e4t, jotta osaat varautua oikeisiin uhkiin.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"usein-kysytyt-kysymykset\">Usein kysytyt kysymykset<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pitaisiko-kayttaa-sessioita-vai-jwtta\">Pit\u00e4isik\u00f6 k\u00e4ytt\u00e4\u00e4 sessioita vai JWT:t\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Selainpohjaisissa sovelluksissa, joissa tarvitaan kirjautuminen ja istunnon kumoaminen, palvelinpohjainen sessio on yleens\u00e4 turvallisempi ja yksinkertaisempi valinta. JWT loistaa tilattomissa API:issa ja palvelujenv\u00e4lisess\u00e4 liikenteess\u00e4, jossa jaettua tilaa ei haluta. Monet sovellukset k\u00e4ytt\u00e4v\u00e4t molempia: sessioita selaimelle ja JWT:t\u00e4 koneiden v\u00e4liseen liikenteeseen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"toimiiko-tama-node-js-24lla\">Toimiiko t\u00e4m\u00e4 Node.js 24:ll\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Kyll\u00e4, koodi toimii my\u00f6s Node.js 24:ll\u00e4, mutta tuotantoon suosittelemme Node.js 22 LTS:\u00e4\u00e4, koska se on aktiivinen LTS-julkaisu ja saa tietoturvap\u00e4ivityksi\u00e4 huhtikuuhun 2027 asti. LTS-linja on yritysymp\u00e4rist\u00f6jen vakio-oletus ja v\u00e4hent\u00e4\u00e4 yll\u00e4tt\u00e4vien rikkoutumisten riski\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"tarvitsenko-erillisen-csrf-suojan-jos-kaytan-samesitea\">Tarvitsenko erillisen CSRF-suojan, jos k\u00e4yt\u00e4n SameSitea?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>SameSite=lax<\/code> torjuu monia CSRF-hy\u00f6kk\u00e4yksi\u00e4, mutta se ei korvaa t\u00e4ysin erillist\u00e4 CSRF-tokenia kaikissa tilanteissa, etenk\u00e4\u00e4n vanhoissa selaimissa tai tietyiss\u00e4 alidomain-skenaarioissa. Turvakriittisiss\u00e4 lomakkeissa kannattaa k\u00e4ytt\u00e4\u00e4 molempia. Aiheesta on oma <a href=\"\/csrf-protection-nodejs\/\">CSRF-suojausoppaamme<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kuinka-pitka-istunnon-vanhenemisajan-pitaisi-olla\">Kuinka pitk\u00e4 istunnon vanhenemisajan pit\u00e4isi olla?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Tasapaino k\u00e4ytett\u00e4vyyden ja turvallisuuden v\u00e4lill\u00e4 ratkaisee. Turvakriittisiss\u00e4 sovelluksissa, kuten verkkopankissa, lyhyt 15-30 minuutin liukuva vanheneminen on tavallinen. Tavallisissa sovelluksissa 1-24 tuntia toimii hyvin. K\u00e4yt\u00e4 <code>rolling: true<\/code>, jotta aktiivinen k\u00e4ytt\u00e4j\u00e4 pysyy kirjautuneena ja vain passiivinen istunto vanhenee.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"voinko-skaalata-taman-usealle-palvelimelle\">Voinko skaalata t\u00e4m\u00e4n usealle palvelimelle?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Kyll\u00e4. Juuri t\u00e4st\u00e4 syyst\u00e4 k\u00e4yt\u00e4mme Redist\u00e4 jaettuna istuntovarastona. Kun kaikki instanssit lukevat ja kirjoittavat saman Redisin, k\u00e4ytt\u00e4j\u00e4 pysyy kirjautuneena riippumatta siit\u00e4, mik\u00e4 palvelin pyynn\u00f6n k\u00e4sittelee. Muista my\u00f6s asettaa <code>trust proxy<\/code>, koska kuormantasaaja on t\u00e4ll\u00f6in pyynt\u00f6jen edess\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"riittaako-httponly-suojaamaan-xsslta\">Riitt\u00e4\u00e4k\u00f6 HttpOnly suojaamaan XSS:lt\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ei yksin. <code>HttpOnly<\/code> est\u00e4\u00e4 ev\u00e4steen lukemisen JavaScriptill\u00e4, mik\u00e4 rajoittaa vahinkoa, mutta XSS-aukko mahdollistaa silti pyynt\u00f6jen tekemisen k\u00e4ytt\u00e4j\u00e4n nimiss\u00e4. Paras suoja on est\u00e4\u00e4 XSS kokonaan sy\u00f6tteen validoinnilla, tulostuksen koodauksella ja Helmetin asettamalla sis\u00e4lt\u00f6turvak\u00e4yt\u00e4nn\u00f6ll\u00e4. <code>HttpOnly<\/code> on t\u00e4rke\u00e4 lis\u00e4kerros, ei ainoa puolustus.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"related-coverage\">Related Coverage<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/jwt-authentication-nodejs\/\">JWT-todennus Node.js:ss\u00e4: 10 vaihetta<\/a><\/li><li><a href=\"\/csrf-protection-nodejs\/\">CSRF-suojaus Node.js:ss\u00e4: 12 vaihetta<\/a><\/li><li><a href=\"\/kaksivaiheinen-tunnistautuminen-vertailu\/\">2FA-vertailu: 5 tapaa ja 99,9 % suoja<\/a><\/li><li><a href=\"\/salasanaturvallisuus\/\">Salasanaturvallisuus: vahvat salasanat ja niiden suojaus<\/a><\/li><li><a href=\"\/https-ja-tls\/\">HTTPS ja TLS: miten salattu yhteys suojaa sinua<\/a><\/li><li><a href=\"\/wireguard-vpn-palvelin-linux\/\">WireGuard VPN Linuxille: oma palvelin 12 vaiheessa<\/a><\/li><li><a href=\"\/security-hub\/\">Verkkoturvallisuus: opas digitaalisen el\u00e4m\u00e4n suojaamiseen<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><em>T\u00e4m\u00e4 opas on tarkoitettu opetukseen ja sovellusten suojaamiseen. Testaa istunnonhallinta omassa ymp\u00e4rist\u00f6ss\u00e4si ja seuraa riippuvuuksien tietoturvap\u00e4ivityksi\u00e4. Julkaistu 14. kes\u00e4kuuta 2026.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Istunto eli sessio on se hauras lanka, joka pit\u00e4\u00e4 k\u00e4ytt\u00e4j\u00e4n kirjautuneena verkkosovellukseen pyynn\u00f6st\u00e4 toiseen. Jos lanka katkeaa v\u00e4\u00e4r\u00e4ll\u00e4 tavalla, hy\u00f6kk\u00e4\u00e4j\u00e4 voi varastaa toisen k\u00e4ytt\u00e4j\u00e4n istunnon ja toimia t\u00e4m\u00e4n nimiss\u00e4 ilman\u2026<\/p>\n","protected":false},"author":5,"featured_media":81,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,3],"tags":[],"class_list":["post-80","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/80","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/comments?post=80"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/80\/revisions"}],"predecessor-version":[{"id":82,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/80\/revisions\/82"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media\/81"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media?parent=80"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/categories?post=80"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/tags?post=80"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}