From 4893d50321a00dae5619cd80cadf19f6d5e0d2cb Mon Sep 17 00:00:00 2001
From: Steel <mael.acier@ensiie.fr>
Date: Mon, 11 Dec 2023 15:41:02 +0100
Subject: [PATCH] publish

---
 .gitlab-ci.yml                     |  16 ++
 .npmrc                             |   3 +-
 package.json                       |  14 +-
 pnpm-lock.yaml                     | 336 ++++++++++++++++++++++++++++-
 src/app.d.ts                       |   7 +-
 src/lib/cookies.ts                 |  50 +++++
 src/lib/env.ts                     |  13 ++
 src/lib/handlers/cookie.ts         |  32 +++
 src/lib/handlers/index.ts          |  35 +++
 src/lib/handlers/login.ts          |  31 +++
 src/lib/handlers/loginCallback.ts  |  53 +++++
 src/lib/handlers/logout.ts         |  19 ++
 src/lib/handlers/logoutCallback.ts |  16 ++
 src/lib/index.ts                   |  39 +++-
 src/lib/paths.ts                   |   5 +
 src/lib/types.ts                   |  48 +++++
 src/lib/utils/env.ts               |  18 ++
 src/lib/utils/jwt_cookie.ts        |  75 +++++++
 src/lib/utils/public_env.ts        |  21 ++
 19 files changed, 821 insertions(+), 10 deletions(-)
 create mode 100644 src/lib/cookies.ts
 create mode 100644 src/lib/env.ts
 create mode 100644 src/lib/handlers/cookie.ts
 create mode 100644 src/lib/handlers/index.ts
 create mode 100644 src/lib/handlers/login.ts
 create mode 100644 src/lib/handlers/loginCallback.ts
 create mode 100644 src/lib/handlers/logout.ts
 create mode 100644 src/lib/handlers/logoutCallback.ts
 create mode 100644 src/lib/paths.ts
 create mode 100644 src/lib/types.ts
 create mode 100644 src/lib/utils/env.ts
 create mode 100644 src/lib/utils/jwt_cookie.ts
 create mode 100644 src/lib/utils/public_env.ts

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dce5193..dfa0ce1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,7 @@
 stages:
   - build
   - test
+  - deploy
 
 # This folder is cached between builds
 # https://docs.gitlab.com/ee/ci/yaml/index.html#cache
@@ -21,6 +22,11 @@ build:
   extends: .pnpm
   script:
     - pnpm build
+  artifacts:
+    untracked: false
+    when: on_success
+    paths:
+      - dist/*
 
 lint:
   stage: test
@@ -34,3 +40,13 @@ check:
   script:
     - pnpm build
     - pnpm check
+
+publish-npm:
+  stage: deploy
+  extends: .pnpm
+  dependencies:
+    - build
+  script:
+    - echo "@arise:registry=https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" >> .npmrc
+    - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
+    - npm publish
diff --git a/.npmrc b/.npmrc
index b6f27f1..67f4525 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
-engine-strict=true
+# https://github.com/renovatebot/renovate/issues/12068#issuecomment-939236784
+# engine-strict=true
diff --git a/package.json b/package.json
index 7483902..0001709 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "sveltekit",
+  "name": "ariseid-connect-sveltekit",
   "version": "0.0.1",
   "scripts": {
     "dev": "vite dev",
@@ -31,6 +31,8 @@
     "@sveltejs/adapter-auto": "^2.0.0",
     "@sveltejs/kit": "^1.27.4",
     "@sveltejs/package": "^2.0.0",
+    "@types/cookie": "^0.6.0",
+    "@types/jsonwebtoken": "^9.0.5",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.28.0",
@@ -48,5 +50,13 @@
   },
   "svelte": "./dist/index.js",
   "types": "./dist/index.d.ts",
-  "type": "module"
+  "type": "module",
+  "dependencies": {
+    "assert": "^2.1.0",
+    "cookie": "^0.6.0",
+    "http-status": "^1.7.3",
+    "jsonwebtoken": "^9.0.2",
+    "openid-client": "^5.6.1",
+    "zod": "^3.22.4"
+  }
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 22cd6ea..6e48e32 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,6 +4,26 @@ settings:
   autoInstallPeers: true
   excludeLinksFromLockfile: false
 
+dependencies:
+  assert:
+    specifier: ^2.1.0
+    version: 2.1.0
+  cookie:
+    specifier: ^0.6.0
+    version: 0.6.0
+  http-status:
+    specifier: ^1.7.3
+    version: 1.7.3
+  jsonwebtoken:
+    specifier: ^9.0.2
+    version: 9.0.2
+  openid-client:
+    specifier: ^5.6.1
+    version: 5.6.1
+  zod:
+    specifier: ^3.22.4
+    version: 3.22.4
+
 devDependencies:
   '@sveltejs/adapter-auto':
     specifier: ^2.0.0
@@ -14,6 +34,12 @@ devDependencies:
   '@sveltejs/package':
     specifier: ^2.0.0
     version: 2.2.3(svelte@4.2.8)(typescript@5.3.3)
+  '@types/cookie':
+    specifier: ^0.6.0
+    version: 0.6.0
+  '@types/jsonwebtoken':
+    specifier: ^9.0.5
+    version: 9.0.5
   '@typescript-eslint/eslint-plugin':
     specifier: ^6.0.0
     version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.3)
@@ -502,6 +528,10 @@ packages:
     resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==}
     dev: true
 
+  /@types/cookie@0.6.0:
+    resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+    dev: true
+
   /@types/estree@1.0.5:
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
     dev: true
@@ -510,6 +540,12 @@ packages:
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
     dev: true
 
+  /@types/jsonwebtoken@9.0.5:
+    resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==}
+    dependencies:
+      '@types/node': 20.10.4
+    dev: true
+
   /@types/node@20.10.4:
     resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==}
     dependencies:
@@ -765,10 +801,25 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /assert@2.1.0:
+    resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
+    dependencies:
+      call-bind: 1.0.5
+      is-nan: 1.3.2
+      object-is: 1.1.5
+      object.assign: 4.1.5
+      util: 0.12.5
+    dev: false
+
   /assertion-error@1.1.0:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
     dev: true
 
+  /available-typed-arrays@1.0.5:
+    resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
   /axobject-query@3.2.1:
     resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
     dependencies:
@@ -808,11 +859,23 @@ packages:
     resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
     dev: true
 
+  /buffer-equal-constant-time@1.0.1:
+    resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+    dev: false
+
   /cac@6.7.14:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
     engines: {node: '>=8'}
     dev: true
 
+  /call-bind@1.0.5:
+    resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
+    dependencies:
+      function-bind: 1.1.2
+      get-intrinsic: 1.2.2
+      set-function-length: 1.1.1
+    dev: false
+
   /callsites@3.1.0:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
@@ -890,6 +953,11 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
+  /cookie@0.6.0:
+    resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
+    engines: {node: '>= 0.6'}
+    dev: false
+
   /cross-spawn@7.0.3:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
@@ -945,6 +1013,24 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /define-data-property@1.1.1:
+    resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      get-intrinsic: 1.2.2
+      gopd: 1.0.1
+      has-property-descriptors: 1.0.1
+    dev: false
+
+  /define-properties@1.2.1:
+    resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      define-data-property: 1.1.1
+      has-property-descriptors: 1.0.1
+      object-keys: 1.1.1
+    dev: false
+
   /dequal@2.0.3:
     resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
     engines: {node: '>=6'}
@@ -978,6 +1064,12 @@ packages:
       esutils: 2.0.3
     dev: true
 
+  /ecdsa-sig-formatter@1.0.11:
+    resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+    dependencies:
+      safe-buffer: 5.2.1
+    dev: false
+
   /es6-promise@3.3.1:
     resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
     dev: true
@@ -1231,6 +1323,12 @@ packages:
     resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
     dev: true
 
+  /for-each@0.3.3:
+    resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+    dependencies:
+      is-callable: 1.2.7
+    dev: false
+
   /fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
     dev: true
@@ -1243,10 +1341,23 @@ packages:
     dev: true
     optional: true
 
+  /function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+    dev: false
+
   /get-func-name@2.0.2:
     resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
     dev: true
 
+  /get-intrinsic@1.2.2:
+    resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
+    dependencies:
+      function-bind: 1.1.2
+      has-proto: 1.0.1
+      has-symbols: 1.0.3
+      hasown: 2.0.0
+    dev: false
+
   /glob-parent@5.1.2:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
@@ -1310,6 +1421,12 @@ packages:
     resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
     dev: true
 
+  /gopd@1.0.1:
+    resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+    dependencies:
+      get-intrinsic: 1.2.2
+    dev: false
+
   /graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
     dev: true
@@ -1323,6 +1440,41 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /has-property-descriptors@1.0.1:
+    resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
+    dependencies:
+      get-intrinsic: 1.2.2
+    dev: false
+
+  /has-proto@1.0.1:
+    resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
+  /has-symbols@1.0.3:
+    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
+  /has-tostringtag@1.0.0:
+    resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      has-symbols: 1.0.3
+    dev: false
+
+  /hasown@2.0.0:
+    resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      function-bind: 1.1.2
+    dev: false
+
+  /http-status@1.7.3:
+    resolution: {integrity: sha512-GS8tL1qHT2nBCMJDYMHGkkkKQLNkIAHz37vgO68XKvzv+XyqB4oh/DfmMHdtRzfqSJPj1xKG2TaELZtlCz6BEQ==}
+    engines: {node: '>= 0.4.0'}
+    dev: false
+
   /ignore-walk@5.0.1:
     resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -1361,7 +1513,14 @@ packages:
 
   /inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-    dev: true
+
+  /is-arguments@1.1.1:
+    resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.5
+      has-tostringtag: 1.0.0
+    dev: false
 
   /is-binary-path@2.1.0:
     resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
@@ -1370,11 +1529,23 @@ packages:
       binary-extensions: 2.2.0
     dev: true
 
+  /is-callable@1.2.7:
+    resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
   /is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /is-generator-function@1.0.10:
+    resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      has-tostringtag: 1.0.0
+    dev: false
+
   /is-glob@4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
@@ -1382,6 +1553,14 @@ packages:
       is-extglob: 2.1.1
     dev: true
 
+  /is-nan@1.3.2:
+    resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.5
+      define-properties: 1.2.1
+    dev: false
+
   /is-number@7.0.0:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
@@ -1398,10 +1577,21 @@ packages:
       '@types/estree': 1.0.5
     dev: true
 
+  /is-typed-array@1.1.12:
+    resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      which-typed-array: 1.1.13
+    dev: false
+
   /isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
     dev: true
 
+  /jose@4.15.4:
+    resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==}
+    dev: false
+
   /js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -1425,6 +1615,37 @@ packages:
     resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
     dev: true
 
+  /jsonwebtoken@9.0.2:
+    resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
+    engines: {node: '>=12', npm: '>=6'}
+    dependencies:
+      jws: 3.2.2
+      lodash.includes: 4.3.0
+      lodash.isboolean: 3.0.3
+      lodash.isinteger: 4.0.4
+      lodash.isnumber: 3.0.3
+      lodash.isplainobject: 4.0.6
+      lodash.isstring: 4.0.1
+      lodash.once: 4.1.1
+      ms: 2.1.2
+      semver: 7.5.4
+    dev: false
+
+  /jwa@1.4.1:
+    resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
+    dependencies:
+      buffer-equal-constant-time: 1.0.1
+      ecdsa-sig-formatter: 1.0.11
+      safe-buffer: 5.2.1
+    dev: false
+
+  /jws@3.2.2:
+    resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
+    dependencies:
+      jwa: 1.4.1
+      safe-buffer: 5.2.1
+    dev: false
+
   /keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
     dependencies:
@@ -1469,10 +1690,38 @@ packages:
       p-locate: 5.0.0
     dev: true
 
+  /lodash.includes@4.3.0:
+    resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+    dev: false
+
+  /lodash.isboolean@3.0.3:
+    resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
+    dev: false
+
+  /lodash.isinteger@4.0.4:
+    resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
+    dev: false
+
+  /lodash.isnumber@3.0.3:
+    resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
+    dev: false
+
+  /lodash.isplainobject@4.0.6:
+    resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+    dev: false
+
+  /lodash.isstring@4.0.1:
+    resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
+    dev: false
+
   /lodash.merge@4.6.2:
     resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
     dev: true
 
+  /lodash.once@4.1.1:
+    resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+    dev: false
+
   /loupe@2.3.7:
     resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
     dependencies:
@@ -1490,7 +1739,6 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       yallist: 4.0.0
-    dev: true
 
   /magic-string@0.27.0:
     resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
@@ -1573,7 +1821,6 @@ packages:
 
   /ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
-    dev: true
 
   /nanoid@3.3.7:
     resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
@@ -1620,12 +1867,54 @@ packages:
       npm-normalize-package-bin: 2.0.0
     dev: true
 
+  /object-hash@2.2.0:
+    resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
+    engines: {node: '>= 6'}
+    dev: false
+
+  /object-is@1.1.5:
+    resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.5
+      define-properties: 1.2.1
+    dev: false
+
+  /object-keys@1.1.1:
+    resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
+  /object.assign@4.1.5:
+    resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.5
+      define-properties: 1.2.1
+      has-symbols: 1.0.3
+      object-keys: 1.1.1
+    dev: false
+
+  /oidc-token-hash@5.0.3:
+    resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==}
+    engines: {node: ^10.13.0 || >=12.0.0}
+    dev: false
+
   /once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
     dependencies:
       wrappy: 1.0.2
     dev: true
 
+  /openid-client@5.6.1:
+    resolution: {integrity: sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==}
+    dependencies:
+      jose: 4.15.4
+      lru-cache: 6.0.0
+      object-hash: 2.2.0
+      oidc-token-hash: 5.0.3
+    dev: false
+
   /optionator@0.9.3:
     resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
     engines: {node: '>= 0.8.0'}
@@ -1883,6 +2172,10 @@ packages:
       mri: 1.2.0
     dev: true
 
+  /safe-buffer@5.2.1:
+    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+    dev: false
+
   /sander@0.5.1:
     resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
     dependencies:
@@ -1898,12 +2191,21 @@ packages:
     hasBin: true
     dependencies:
       lru-cache: 6.0.0
-    dev: true
 
   /set-cookie-parser@2.6.0:
     resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
     dev: true
 
+  /set-function-length@1.1.1:
+    resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      define-data-property: 1.1.1
+      get-intrinsic: 1.2.2
+      gopd: 1.0.1
+      has-property-descriptors: 1.0.1
+    dev: false
+
   /shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -2219,6 +2521,16 @@ packages:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
     dev: true
 
+  /util@0.12.5:
+    resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
+    dependencies:
+      inherits: 2.0.4
+      is-arguments: 1.1.1
+      is-generator-function: 1.0.10
+      is-typed-array: 1.1.12
+      which-typed-array: 1.1.13
+    dev: false
+
   /vite-node@0.34.6(@types/node@20.10.4):
     resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
     engines: {node: '>=v14.18.0'}
@@ -2353,6 +2665,17 @@ packages:
       - terser
     dev: true
 
+  /which-typed-array@1.1.13:
+    resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      available-typed-arrays: 1.0.5
+      call-bind: 1.0.5
+      for-each: 0.3.3
+      gopd: 1.0.1
+      has-tostringtag: 1.0.0
+    dev: false
+
   /which@2.0.2:
     resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
     engines: {node: '>= 8'}
@@ -2376,7 +2699,6 @@ packages:
 
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
-    dev: true
 
   /yaml@1.10.2:
     resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
@@ -2392,3 +2714,7 @@ packages:
     resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
     engines: {node: '>=12.20'}
     dev: true
+
+  /zod@3.22.4:
+    resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
+    dev: false
diff --git a/src/app.d.ts b/src/app.d.ts
index 899c7e8..0ae5781 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -1,9 +1,14 @@
 // See https://kit.svelte.dev/docs/types#app
+
+import type { AriseData } from "$lib/index.ts";
+
 // for information about these interfaces
 declare global {
   namespace App {
     // interface Error {}
-    // interface Locals {}
+    interface Locals {
+      arise: AriseData;
+    }
     // interface PageData {}
     // interface Platform {}
   }
diff --git a/src/lib/cookies.ts b/src/lib/cookies.ts
new file mode 100644
index 0000000..feb8630
--- /dev/null
+++ b/src/lib/cookies.ts
@@ -0,0 +1,50 @@
+import { JWTCookieManager } from "$lib/utils/jwt_cookie.js";
+import type { Cookies } from "@sveltejs/kit";
+import type { TokenSet } from "openid-client";
+import { z } from "zod";
+import type { InternalConfig } from "$lib/types.js";
+
+const ONE_HOUR = 60 * 60;
+const ONE_DAY = 24 * ONE_HOUR;
+const ONE_MONTH = 30 * ONE_DAY;
+
+export const authSchema = z.object({
+  codeVerifier: z.string(),
+  state: z.string(),
+  nonce: z.string(),
+});
+
+export const authCookie = new JWTCookieManager(authSchema, {
+  serialize: { path: "/", maxAge: ONE_HOUR },
+  name: "tmp_arise_auth_secrets",
+});
+
+export const tokenSetSchema = z.object({
+  access_token: z.string().optional(),
+  token_type: z.string().optional(),
+  id_token: z.string().optional(),
+  refresh_token: z.string().optional(),
+  scope: z.string().optional(),
+  expires_at: z.number().optional(),
+  session_state: z.string().optional(),
+});
+
+export const tokenSetCookie = new JWTCookieManager(tokenSetSchema, {
+  name: "arise_token_set",
+  serialize: {
+    maxAge: ONE_MONTH,
+  },
+});
+
+export async function setTokenSetCookie(
+  tokenSet: TokenSet,
+  cookies: Cookies,
+  { client }: InternalConfig,
+) {
+  const userinfo = await client.userinfo(tokenSet);
+
+  tokenSetCookie.send(cookies, {
+    payload: tokenSet,
+    jwt: { subject: userinfo.sub, expiresIn: ONE_MONTH },
+  });
+}
diff --git a/src/lib/env.ts b/src/lib/env.ts
new file mode 100644
index 0000000..1eae230
--- /dev/null
+++ b/src/lib/env.ts
@@ -0,0 +1,13 @@
+import { ensureEnv } from "$lib/utils/env.js";
+import { ensurePublicEnv } from "$lib/utils/public_env.js";
+
+export const env = ensureEnv([
+  "API_URL",
+  "API_TOKEN",
+  "JWT_SECRET",
+  "AIDC_CLIENT_ID",
+  "AIDC_CLIENT_SECRET",
+  "ORIGIN",
+] as const);
+
+export const publicEnv = ensurePublicEnv(["PUBLIC_MAPTILER_KEY"] as const);
diff --git a/src/lib/handlers/cookie.ts b/src/lib/handlers/cookie.ts
new file mode 100644
index 0000000..6ec9052
--- /dev/null
+++ b/src/lib/handlers/cookie.ts
@@ -0,0 +1,32 @@
+import { TokenSet } from "openid-client";
+import { setTokenSetCookie, tokenSetCookie } from "$lib/cookies.js";
+import type { Handle } from "@sveltejs/kit";
+import type { InternalConfig } from "$lib/types.js";
+
+export default function (config: InternalConfig): Handle {
+  return async function ({ event, resolve }) {
+    const jwtTokenSet = tokenSetCookie.receive(event.cookies);
+
+    if (jwtTokenSet !== null) {
+      let tokenSet = new TokenSet(jwtTokenSet);
+
+      try {
+        if (tokenSet.expired()) {
+          tokenSet = await config.client.refresh(tokenSet);
+
+          await setTokenSetCookie(tokenSet, event.cookies, config);
+        }
+
+        event.locals.arise.loggedIn = {
+          user: { id: tokenSet.claims().sub },
+          tokenSet,
+        };
+      } catch (error) {
+        console.error(error);
+        tokenSetCookie.delete(event.cookies);
+      }
+    }
+
+    return resolve(event);
+  };
+}
diff --git a/src/lib/handlers/index.ts b/src/lib/handlers/index.ts
new file mode 100644
index 0000000..031158d
--- /dev/null
+++ b/src/lib/handlers/index.ts
@@ -0,0 +1,35 @@
+import { sequence } from "@sveltejs/kit/hooks";
+import cookieHandler from "./cookie";
+import loginHandler from "./login";
+import logoutHandler from "./logout";
+import loginCallbackHandler from "./loginCallback";
+import logoutCallbackHandler from "./logoutCallback";
+import type { Handle } from "@sveltejs/kit";
+import type { InternalConfig } from "../types";
+
+const init: Handle = ({ event, resolve }) => {
+  event.locals.arise = {};
+  return resolve(event);
+};
+
+function route(pathname: string, handler: Handle): Handle {
+  return ({ event, resolve }) => {
+    if (event.url.pathname === pathname) {
+      return handler({ event, resolve });
+    }
+
+    return resolve(event);
+  };
+}
+
+export default function (config: InternalConfig): Handle {
+  const { paths } = config;
+  return sequence(
+    init,
+    cookieHandler(config),
+    route(paths.login, loginHandler(config)),
+    route(paths.logout, logoutHandler(config)),
+    route(paths.callback, loginCallbackHandler(config)),
+    route(paths.logoutCallback, logoutCallbackHandler(config)),
+  );
+}
diff --git a/src/lib/handlers/login.ts b/src/lib/handlers/login.ts
new file mode 100644
index 0000000..c1dca77
--- /dev/null
+++ b/src/lib/handlers/login.ts
@@ -0,0 +1,31 @@
+import { redirect } from "@sveltejs/kit";
+import { generators } from "openid-client";
+import { authCookie } from "../cookies";
+import type { Handle } from "@sveltejs/kit";
+import type { InternalConfig } from "../types";
+import { SEE_OTHER } from "http-status";
+
+export default function (config: InternalConfig): Handle {
+  return async function ({ event }) {
+    const codeVerifier = generators.codeVerifier();
+    const state = generators.state();
+    const nonce = generators.nonce();
+
+    authCookie.send(event.cookies, {
+      payload: { codeVerifier, state, nonce },
+    });
+
+    const redirectURI = new URL(config.paths.callback, event.url).toString();
+    const authorizationUrl = config.client.authorizationUrl({
+      response_type: "code",
+      scope: config.scope,
+      code_challenge: generators.codeChallenge(codeVerifier),
+      code_challenge_method: "S256",
+      redirect_uri: redirectURI,
+      state,
+      nonce,
+    });
+
+    throw redirect(SEE_OTHER, authorizationUrl);
+  };
+}
diff --git a/src/lib/handlers/loginCallback.ts b/src/lib/handlers/loginCallback.ts
new file mode 100644
index 0000000..1de08b7
--- /dev/null
+++ b/src/lib/handlers/loginCallback.ts
@@ -0,0 +1,53 @@
+import { type Handle, redirect } from "@sveltejs/kit";
+import { authCookie, setTokenSetCookie } from "../cookies";
+import { errors } from "openid-client";
+import type { InternalConfig } from "../types";
+import { base } from "$app/paths";
+import { SEE_OTHER } from "http-status";
+
+export default function (config: InternalConfig): Handle {
+  return async function ({ event, resolve }) {
+    const params = config.client.callbackParams(event.url.toString());
+
+    const cookie = authCookie.receive(event.cookies);
+
+    if (cookie === null) {
+      throw redirect(SEE_OTHER, config.paths.login);
+    }
+
+    try {
+      const redirectURI = new URL(config.paths.callback, event.url).toString();
+      const tokenSet = await config.client.callback(redirectURI, params, {
+        code_verifier: cookie.codeVerifier,
+        state: cookie.state,
+        nonce: cookie.nonce,
+      });
+
+      await setTokenSetCookie(tokenSet, event.cookies, config);
+    } catch (error) {
+      if (error instanceof errors.OPError) {
+        if (error.error === "access_denied") {
+          if (config.on?.accessDenied) {
+            return config.on.accessDenied(event, error);
+          }
+
+          throw redirect(SEE_OTHER, `${base}/`);
+        }
+
+        event.locals.arise.error = error;
+        return resolve(event);
+      } else if (error instanceof errors.RPError) {
+        event.locals.arise.error = error;
+        return resolve(event);
+      }
+
+      throw error;
+    }
+
+    if (config.on?.loggedIn) {
+      return config.on.loggedIn(event);
+    }
+
+    throw redirect(SEE_OTHER, `${base}/`);
+  };
+}
diff --git a/src/lib/handlers/logout.ts b/src/lib/handlers/logout.ts
new file mode 100644
index 0000000..b0f280d
--- /dev/null
+++ b/src/lib/handlers/logout.ts
@@ -0,0 +1,19 @@
+import { redirect } from "@sveltejs/kit";
+import type { Handle } from "@sveltejs/kit";
+import type { InternalConfig } from "../types";
+import { SEE_OTHER } from "http-status";
+
+export default function (config: InternalConfig): Handle {
+  return async function ({ event }) {
+    const postLogoutRedirectUTI = new URL(
+      config.paths.logoutCallback,
+      event.url,
+    ).toString();
+    const endSessionUrl = config.client.endSessionUrl({
+      post_logout_redirect_uri: postLogoutRedirectUTI,
+      id_token_hint: event.locals.arise.loggedIn?.tokenSet,
+    });
+
+    throw redirect(SEE_OTHER, endSessionUrl);
+  };
+}
diff --git a/src/lib/handlers/logoutCallback.ts b/src/lib/handlers/logoutCallback.ts
new file mode 100644
index 0000000..0452d65
--- /dev/null
+++ b/src/lib/handlers/logoutCallback.ts
@@ -0,0 +1,16 @@
+import { type Handle, redirect } from "@sveltejs/kit";
+import { tokenSetCookie } from "../cookies";
+import type { InternalConfig } from "../types";
+import { SEE_OTHER } from "http-status";
+
+export default function (config: InternalConfig): Handle {
+  return async function ({ event }) {
+    tokenSetCookie.delete(event.cookies);
+
+    if (config.on?.loggedOut) {
+      return config.on.loggedOut(event);
+    }
+
+    throw redirect(SEE_OTHER, "/");
+  };
+}
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 47d3c46..b70ab83 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1 +1,38 @@
-// Reexport your entry components here
+import { Issuer } from "openid-client";
+import { base } from "$app/paths";
+import handler from "./handlers/index.js";
+import type { Config } from "./types.js";
+import * as paths from "./paths.js";
+
+export type { AuthData as AriseData, Config, Locals } from "./types.js";
+export { handleBuilder as ariseIdConnectBuilder } from "./index.js";
+export * as authPaths from "./paths.js";
+
+function addBase(path: string): string {
+  return `${base}${path}`;
+}
+
+export async function handleBuilder(config: Config) {
+  const issuer = await Issuer.discover(
+    config.issuer || "https://oidc.iiens.net/.well-known/openid-configuration",
+  );
+
+  const client = new issuer.Client({
+    ...config,
+    grant_types: ["authorization_code", "refresh_token"],
+    response_types: ["code", "id_token"],
+  });
+
+  return handler({
+    ...config,
+    paths: {
+      callback: addBase(config.paths?.callback ?? paths.CALLBACK),
+      logoutCallback: addBase(
+        config.paths?.logoutCallback ?? paths.LOGOUT_CALLBACK,
+      ),
+      login: addBase(config.paths?.login ?? paths.LOGIN),
+      logout: addBase(config.paths?.logout ?? paths.LOGOUT),
+    },
+    client,
+  });
+}
diff --git a/src/lib/paths.ts b/src/lib/paths.ts
new file mode 100644
index 0000000..1b28a50
--- /dev/null
+++ b/src/lib/paths.ts
@@ -0,0 +1,5 @@
+export const BASE = "/auth";
+export const CALLBACK = `${BASE}/callback`;
+export const LOGOUT_CALLBACK = `${BASE}/callback/logout`;
+export const LOGIN = `${BASE}/login`;
+export const LOGOUT = `${BASE}/logout`;
diff --git a/src/lib/types.ts b/src/lib/types.ts
new file mode 100644
index 0000000..5237925
--- /dev/null
+++ b/src/lib/types.ts
@@ -0,0 +1,48 @@
+import type { MaybePromise, RequestEvent } from "@sveltejs/kit";
+import type {
+  BaseClient,
+  ClientMetadata,
+  errors,
+  TokenSet,
+} from "openid-client";
+
+export type AuthData = {
+  loggedIn?: {
+    user: {
+      id: string;
+    };
+    tokenSet: TokenSet;
+  };
+  error?: errors.OPError | errors.RPError;
+};
+
+export interface Locals {
+  arise: AuthData;
+}
+
+type Paths = {
+  callback: string;
+  logoutCallback: string;
+  login: string;
+  logout: string;
+};
+
+export interface Config extends ClientMetadata {
+  client_secret: string;
+  scope: string;
+  paths?: Partial<Paths>;
+  issuer?: string;
+  on?: {
+    accessDenied?: (
+      event: RequestEvent,
+      error: errors.OPError,
+    ) => MaybePromise<Response>;
+    loggedIn?: (event: RequestEvent) => MaybePromise<Response>;
+    loggedOut?: (event: RequestEvent) => MaybePromise<Response>;
+  };
+}
+
+export interface InternalConfig extends Config {
+  client: BaseClient;
+  paths: Paths;
+}
diff --git a/src/lib/utils/env.ts b/src/lib/utils/env.ts
new file mode 100644
index 0000000..50f5fb6
--- /dev/null
+++ b/src/lib/utils/env.ts
@@ -0,0 +1,18 @@
+import * as assert from "assert";
+import { env } from "$env/dynamic/private";
+
+type RecordFromKeys<T extends readonly string[]> = Record<T[number], string> &
+  Partial<typeof env>;
+
+export function ensureEnv<K extends readonly string[]>(
+  keys: K,
+): RecordFromKeys<K> {
+  for (const key of keys) {
+    const value = env[key];
+    if (!env.CI) {
+      assert.ok(value, `Variable d'environnement ${key} manquante`);
+    }
+  }
+
+  return env as RecordFromKeys<K>;
+}
diff --git a/src/lib/utils/jwt_cookie.ts b/src/lib/utils/jwt_cookie.ts
new file mode 100644
index 0000000..04b98d8
--- /dev/null
+++ b/src/lib/utils/jwt_cookie.ts
@@ -0,0 +1,75 @@
+import type { Cookies } from "@sveltejs/kit";
+import jwt from "jsonwebtoken";
+import { env } from "$lib/env.js";
+import type { z, ZodObject, ZodSchema } from "zod";
+import type { CookieSerializeOptions } from "cookie";
+import { dev } from "$app/environment";
+
+type Empty = Record<string, never>;
+
+export type JWT<T extends ZodSchema = ZodObject<Empty>> = jwt.JwtPayload &
+  z.output<T>;
+
+export interface FlashSendOptions<T> {
+  payload: T;
+  jwt?: jwt.SignOptions;
+}
+
+export interface CookieOptions {
+  serialize?: CookieSerializeOptions;
+  name?: string;
+}
+
+export class JWTCookieManager<T extends ZodSchema = ZodObject<Empty>> {
+  constructor(
+    private schema: T,
+    private cookieOptions?: CookieOptions,
+  ) {}
+
+  protected cookieName(): string {
+    return this.cookieOptions?.name ?? "flash";
+  }
+  protected cookieSerializeOptions(): CookieSerializeOptions {
+    return {
+      path: "/",
+      httpOnly: true,
+      secure: !dev,
+      sameSite: !dev,
+      ...this.cookieOptions?.serialize,
+    };
+  }
+
+  send(cookies: Cookies, options: FlashSendOptions<z.output<T>>) {
+    const payload = this.schema.safeParse(options.payload);
+
+    if (!payload.success) {
+      throw payload.error;
+    }
+
+    const cookie = jwt.sign(payload.data, env.JWT_SECRET, options.jwt);
+    cookies.set(this.cookieName(), cookie, this.cookieSerializeOptions());
+  }
+
+  receive(cookies: Cookies): JWT<T> | null {
+    const token = cookies.get(this.cookieName());
+
+    if (!token) return null;
+
+    try {
+      jwt.verify(token, env.JWT_SECRET);
+    } catch {
+      return null;
+    }
+
+    const payload = jwt.decode(token, { json: true });
+    if (payload) {
+      const { success } = this.schema.safeParse(payload);
+      return success ? (payload as JWT<T>) : null;
+    }
+    return null;
+  }
+
+  delete(cookies: Cookies) {
+    cookies.delete(this.cookieName(), this.cookieSerializeOptions());
+  }
+}
diff --git a/src/lib/utils/public_env.ts b/src/lib/utils/public_env.ts
new file mode 100644
index 0000000..c7085c4
--- /dev/null
+++ b/src/lib/utils/public_env.ts
@@ -0,0 +1,21 @@
+import * as assert from "assert";
+import { env } from "$env/dynamic/public";
+import { browser } from "$app/environment";
+
+type RecordFromKeys<T extends readonly string[]> = Record<T[number], string> &
+  Partial<typeof env>;
+
+export function ensurePublicEnv<K extends readonly `PUBLIC_${string}`[]>(
+  keys: K,
+): RecordFromKeys<K> {
+  for (const key of keys) {
+    const value = env[key];
+    if (!browser) {
+      assert.ok(value, `Variable d'environnement ${key} manquante`);
+    } else {
+      console.error("Variable d'environnement manquante");
+    }
+  }
+
+  return env as RecordFromKeys<K>;
+}
-- 
GitLab