{"id":1764,"date":"2024-04-29T09:22:25","date_gmt":"2024-04-29T15:22:25","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=1764"},"modified":"2024-04-29T13:21:11","modified_gmt":"2024-04-29T19:21:11","slug":"using-nextauth-now-auth-js-with-sveltekit","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/using-nextauth-now-auth-js-with-sveltekit\/","title":{"rendered":"Using Auth.js with SvelteKit"},"content":{"rendered":"\n<p><a href=\"https:\/\/kit.svelte.dev\/\">SvelteKit<\/a> is an exciting framework for shipping performant web applications with Svelte. I&#8217;ve previously <a href=\"https:\/\/css-tricks.com\/getting-started-with-sveltekit\/\">written an introduction on it<\/a>, as well as <a href=\"https:\/\/css-tricks.com\/caching-data-in-sveltekit\/\">a deeper dive on data handling and caching<\/a>.<\/p>\n\n\n\n<p>In this post, we&#8217;ll see how to integrate <a href=\"https:\/\/authjs.dev\/\">Auth.js<\/a> (Previously <code>next-auth<\/code>) into a SvelteKit app. It might seem surprising to hear that this works with SvelteKit, but this project has gotten popular enough that much of it has been split into a framework-agnostic package of\u00a0<a href=\"https:\/\/www.npmjs.com\/package\/@auth\/core\">@auth\/core<\/a>. The Auth.js name is actually a somewhat recent rebranding of NextAuth.<\/p>\n\n\n\n<p>In this post we&#8217;ll cover the basic config for&nbsp;<code>@auth\/core<\/code>: we&#8217;ll add a <a href=\"https:\/\/cloud.google.com\/identity-platform\/docs\/web\/google\">Google Provider<\/a> and configure our sessions to persist in <a href=\"https:\/\/aws.amazon.com\/dynamodb\/\">DynamoDB<\/a>.<\/p>\n\n\n\n<p>The code for everything&nbsp;<a href=\"https:\/\/github.com\/arackaf\/sveltekit-next-auth-post\">is here in a GitHub repo<\/a>, but you won&#8217;t be able to run it without setting up your own Google Application credentials, as well as a Dynamo table (which we&#8217;ll get into).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The initial setup<\/h2>\n\n\n\n<p>We&#8217;ll build the absolute minimum skeleton app needed to demonstrate authentication. We&#8217;ll have our root layout read whether the user is logged in, and show a link to content that&#8217;s limited to logged in users, and a log out button if so; or a log in button if not. We\u2019ll also set up an auth check with redirect in the logged in content, in case the user browses directly to the logged in URL while logged out.<\/p>\n\n\n\n<p>Let&#8217;s create a SvelteKit project if we don&#8217;t have one already, using the insutructions&nbsp;<a href=\"https:\/\/kit.svelte.dev\/docs\/creating-a-project\">here<\/a>. Chose &#8220;Skeleton Project&#8221; when prompted.<\/p>\n\n\n\n<p>Now let&#8217;s install some packages we&#8217;ll be using:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm i @auth\/core @auth\/sveltekit<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s create a top-level layout that will use our auth data. First, our server loader, in a file named&nbsp;<code>+layout.server.ts<\/code>. This will hold our logged-in state, which for now is always false.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({ locals }) =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">loggedIn<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now let&#8217;s make the actual layout, in&nbsp;<code>+layout.svelte<\/code>&nbsp;with some basic markup<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"ts\"<\/span>&gt;<\/span><span class=\"javascript\">\n  <span class=\"hljs-keyword\">import<\/span> type { PageData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/$types'<\/span>;\n  <span class=\"hljs-keyword\">import<\/span> { signIn, signOut } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@auth\/sveltekit\/client'<\/span>;\n\n  <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">let<\/span> data: PageData;\n  $: loggedIn = data.loggedIn;\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Hello there! This is the shared layout.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n  {#if loggedIn}\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Welcome!<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/logged-in\"<\/span>&gt;<\/span>Go to logged in area<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">br<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">br<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">on:click<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> signOut()}&gt;Log Out<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  {:else}\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">on:click<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> signIn('google')}&gt;Log in<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  {\/if}\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">slot<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There should be a root&nbsp;<code>+page.svelte<\/code>&nbsp;file that was generated when you scaffolded the project, with something like this in there<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>This is the home page<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Visit <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/kit.svelte.dev\"<\/span>&gt;<\/span>kit.svelte.dev<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span> to read the SvelteKit docs<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Feel free to just leave it.<\/p>\n\n\n\n<p>Next, we&#8217;ll create a route called&nbsp;<code>logged-in<\/code>. Create a folder in&nbsp;<code>routes<\/code>&nbsp;called&nbsp;<code>logged-in<\/code>&nbsp;and create a&nbsp;<code>+page.server.ts<\/code>&nbsp;which for now will always just redirect you out.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@sveltejs\/kit\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({}) =&gt; {\n  redirect(<span class=\"hljs-number\">302<\/span>, <span class=\"hljs-string\">\"\/\"<\/span>);\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now let&#8217;s create the page itself, in&nbsp;<code>+page.svelte<\/code>&nbsp;and add some markup<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span>&gt;<\/span>This is the logged in page<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And that&#8217;s about it. Check out <a href=\"https:\/\/github.com\/arackaf\/sveltekit-next-auth-post\">the GitHub repo<\/a> to see everything, including just a handful of additional styles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding Auth<\/h2>\n\n\n\n<p>Let&#8217;s get started with the actual authentication.<\/p>\n\n\n\n<p>First, create an environment variable in your .env file called&nbsp;<code>AUTH_SECRET<\/code>&nbsp;and set it to a random string that&#8217;s at least 32 characters. If you&#8217;re looking to deploy this to a host like Vercel or Netlify, be sure to add your environment variable in your project&#8217;s settings according to how that host does things.<\/p>\n\n\n\n<p>Next, create a&nbsp;<code>hooks.server.ts<\/code>&nbsp;(or .js) file directly under&nbsp;<code>src<\/code>. The docs for this file&nbsp;<a href=\"https:\/\/kit.svelte.dev\/docs\/hooks#server-hooks\">are here<\/a>, but it essentially allows you to add application-wide wide side effects. Authentication falls under this, which is why we configure it here.<\/p>\n\n\n\n<p>Now let&#8217;s start integrating auth. We&#8217;ll start with a very basic config:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { SvelteKitAuth } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth\/sveltekit\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { AUTH_SECRET } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$env\/static\/private\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> auth = SvelteKitAuth({\n  <span class=\"hljs-attr\">providers<\/span>: &#91;],\n  <span class=\"hljs-attr\">session<\/span>: {\n    <span class=\"hljs-attr\">maxAge<\/span>: <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span> * <span class=\"hljs-number\">365<\/span>,\n    <span class=\"hljs-attr\">strategy<\/span>: <span class=\"hljs-string\">\"jwt\"<\/span>,\n  },\n\n  <span class=\"hljs-attr\">secret<\/span>: AUTH_SECRET,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> handle = auth.handle;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We tell auth to store our authentication info in <a href=\"https:\/\/jwt.io\/\">a JWT token<\/a>, and configure a max age for the session as 1 year. We provide our secret, and a (currently empty) array of providers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adding our provider<\/h3>\n\n\n\n<p>Providers are what perform the actual authentication of a user. There&#8217;s a very, very long list of options to choose from, which are&nbsp;<a href=\"https:\/\/authjs.dev\/reference\/core\/providers\">listed here<\/a>. We&#8217;ll use Google. First, we&#8217;ll need to create application credentials. So head on over to the&nbsp;<a href=\"https:\/\/console.cloud.google.com\/apis\/dashboard\">Google Developers console<\/a>. Click on credentials, and then &#8220;Create Credentials&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"155\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img1-create-credentials.jpg?resize=1024%2C155&#038;ssl=1\" alt=\"\" class=\"wp-image-1767\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img1-create-credentials.jpg?resize=1024%2C155&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img1-create-credentials.jpg?resize=300%2C45&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img1-create-credentials.jpg?resize=768%2C116&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img1-create-credentials.jpg?w=1468&amp;ssl=1 1468w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Click it, then choose &#8220;OAuth Client ID.&#8221; Choose web application, and give your app a name.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"693\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?resize=693%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1768\" style=\"width:499px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?resize=693%2C1024&amp;ssl=1 693w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?resize=203%2C300&amp;ssl=1 203w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?resize=768%2C1135&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?resize=1039%2C1536&amp;ssl=1 1039w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img2-create-credentials.jpg?w=1112&amp;ssl=1 1112w\" sizes=\"auto, (max-width: 693px) 100vw, 693px\" \/><\/figure>\n<\/div>\n\n\n<p>For now, leave the other options empty, and click Create.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"956\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img3-credentials.jpg?resize=956%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1769\" style=\"width:485px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img3-credentials.jpg?resize=956%2C1024&amp;ssl=1 956w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img3-credentials.jpg?resize=280%2C300&amp;ssl=1 280w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img3-credentials.jpg?resize=768%2C823&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img3-credentials.jpg?w=1036&amp;ssl=1 1036w\" sizes=\"auto, (max-width: 956px) 100vw, 956px\" \/><figcaption class=\"wp-element-caption\">Screenshot<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Before closing that modal, grab the client id, and client secret values, and paste them into environment variables for your app<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"YAML\" data-shcb-language-slug=\"yaml\"><span><code class=\"hljs language-yaml\"><span class=\"hljs-string\">GOOGLE_AUTH_CLIENT_ID=....<\/span>\n<span class=\"hljs-string\">GOOGLE_AUTH_SECRET=....<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">YAML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">yaml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now let&#8217;s go back into our <code>hooks.server.ts<\/code> file, and import our new environment variables:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { AUTH_SECRET, GOOGLE_AUTH_CLIENT_ID, GOOGLE_AUTH_SECRET } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$env\/static\/private\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and then add our provider<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">providers: &#91;\n  GoogleProvider({\n    <span class=\"hljs-attr\">clientId<\/span>: GOOGLE_AUTH_CLIENT_ID,\n    <span class=\"hljs-attr\">clientSecret<\/span>: GOOGLE_AUTH_SECRET\n  })\n],<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and then export our auth handler as our hooks handler.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> handle = auth.handle;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that if you had other handlers you wanted SvelteKit to run, you can use the sequence helper:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { sequence } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@sveltejs\/kit\/hooks\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> handle = sequence(otherHandleFn, auth.handle);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Unfortunately if we try to login now, we&#8217;re greeted by an error:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"868\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img4-bad-login-868x1024-1.jpg?resize=868%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1925\" style=\"width:479px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img4-bad-login-868x1024-1.jpg?w=868&amp;ssl=1 868w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img4-bad-login-868x1024-1.jpg?resize=254%2C300&amp;ssl=1 254w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img4-bad-login-868x1024-1.jpg?resize=768%2C906&amp;ssl=1 768w\" sizes=\"auto, (max-width: 868px) 100vw, 868px\" \/><\/figure>\n<\/div>\n\n\n<p>Clicking error details provides some more info:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"857\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img5-redirect-url.jpg?resize=857%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1771\" style=\"width:508px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img5-redirect-url.jpg?resize=857%2C1024&amp;ssl=1 857w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img5-redirect-url.jpg?resize=251%2C300&amp;ssl=1 251w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img5-redirect-url.jpg?resize=768%2C918&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img5-redirect-url.jpg?w=976&amp;ssl=1 976w\" sizes=\"auto, (max-width: 857px) 100vw, 857px\" \/><\/figure>\n<\/div>\n\n\n<p>We need to tell Google that this redirect URL is in fact valid. Go back to our Google Developer Console, open the credentials we just created, and add this URL in the redirect urls section.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"597\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img6-add-redirect-url.jpg?resize=1024%2C597&#038;ssl=1\" alt=\"\" class=\"wp-image-1772\" style=\"width:573px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img6-add-redirect-url.jpg?resize=1024%2C597&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img6-add-redirect-url.jpg?resize=300%2C175&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img6-add-redirect-url.jpg?resize=768%2C447&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img6-add-redirect-url.jpg?w=1126&amp;ssl=1 1126w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<p>And now, after saving (and possibly waiting a few seconds) we can click login, and see a list of our Google accounts available, and pick the one we want to log in with<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"895\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img7-login.jpg?resize=895%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1773\" style=\"width:466px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img7-login.jpg?resize=895%2C1024&amp;ssl=1 895w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img7-login.jpg?resize=262%2C300&amp;ssl=1 262w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img7-login.jpg?resize=768%2C879&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img7-login.jpg?w=1024&amp;ssl=1 1024w\" sizes=\"auto, (max-width: 895px) 100vw, 895px\" \/><\/figure>\n<\/div>\n\n\n<p>Choosing one of the accounts should log you in, and bring you right back to the same page you were just looking at.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">So you&#8217;ve successfully logged in, now what?<\/h2>\n\n\n\n<p>Being logged in is by itself useless without some way to check logged in state, in order to change content and grant access accordingly. Let&#8217;s go back to our layout&#8217;s server loader<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({ locals }) =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">loggedIn<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We previously pulled in that&nbsp;<code>locals<\/code>&nbsp;property. Auth.js adds a&nbsp;<code>getSession<\/code>&nbsp;method to this, which allows us to grab the current authentication, if any. We just logged in, so let&#8217;s grab the session and see what&#8217;s there<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({ locals }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> session = <span class=\"hljs-keyword\">await<\/span> locals.getSession();\n  <span class=\"hljs-built_in\">console<\/span>.log({ session });\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">loggedIn<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For me, this logs the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"237\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img8-session.jpg?resize=1024%2C237&#038;ssl=1\" alt=\"\" class=\"wp-image-1774\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img8-session.jpg?resize=1024%2C237&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img8-session.jpg?resize=300%2C69&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img8-session.jpg?resize=768%2C177&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img8-session.jpg?w=1472&amp;ssl=1 1472w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>All we need right now is a simple boolean indicating whether the user is logged in, so let&#8217;s send down a boolean on whether the user object exists:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({ locals }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> session = <span class=\"hljs-keyword\">await<\/span> locals.getSession();\n  <span class=\"hljs-keyword\">const<\/span> loggedIn = !!session?.user;\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    loggedIn,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and just like that, our page updates:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"579\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img9-logged-in.jpg?resize=1024%2C579&#038;ssl=1\" alt=\"\" class=\"wp-image-1775\" style=\"width:629px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img9-logged-in.jpg?resize=1024%2C579&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img9-logged-in.jpg?resize=300%2C170&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img9-logged-in.jpg?resize=768%2C434&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img9-logged-in.jpg?w=1354&amp;ssl=1 1354w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<p>The link to our logged-in page still doesn&#8217;t work, since it&#8217;s still always redirecting. We&nbsp;<em>could<\/em>&nbsp;run the&nbsp;<em>same<\/em>&nbsp;code we did before, and call&nbsp;<code>locals.getSession<\/code>&nbsp;to see if the user is logged in. But we already did that, and stored the&nbsp;<code>loggedIn<\/code>&nbsp;property in our layout&#8217;s loader. This makes it available to any routes underneath. So let&#8217;s grab it, and conditionally redirect based on its value.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@sveltejs\/kit\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load = <span class=\"hljs-keyword\">async<\/span> ({ parent }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> parentData = <span class=\"hljs-keyword\">await<\/span> parent();\n\n  <span class=\"hljs-keyword\">if<\/span> (!parentData.loggedIn) {\n    redirect(<span class=\"hljs-number\">302<\/span>, <span class=\"hljs-string\">\"\/\"<\/span>);\n  }\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And now our logged-in page works:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"585\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img10-logged-in-page.jpg?resize=1024%2C585&#038;ssl=1\" alt=\"\" class=\"wp-image-1776\" style=\"width:608px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img10-logged-in-page.jpg?resize=1024%2C585&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img10-logged-in-page.jpg?resize=300%2C171&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img10-logged-in-page.jpg?resize=768%2C439&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img10-logged-in-page.jpg?w=1222&amp;ssl=1 1222w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Persisting our authentication<\/h2>\n\n\n\n<p>We could end this post here. Our authentication works, and we integrated it into application state. Sure, there&#8217;s a myriad of other auth providers (GitHub, Facebook, etc), but those are just variations on the same theme.<\/p>\n\n\n\n<p>But one topic we haven&#8217;t discussed is authentication persistence. Right now our entire session is stored in a JWT, on our user&#8217;s machine. This is convenient, but it does offer some downsides, namely that this data could be stolen. An alternative is to persist our users&#8217; sessions in an external database.&nbsp;<a href=\"https:\/\/www.openidentityplatform.org\/blog\/stateless-vs-stateful-authentication\">This post<\/a>&nbsp;discusses the various tradeoffs, but most of the downsides of stateful (i.e. stored in a database) solutions are complexity and the burden of having to reach out to an external storage to grab session info. Fortunately, Auth.js removes the complexity burden for us. As far as performance concerns, we can choose a storage mechanism that&#8217;s known for being fast and effective: in our case we&#8217;ll look at DynamoDB.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adapters<\/h3>\n\n\n\n<p>The mechanism by which Auth.js persists our authentication sessions is&nbsp;<a href=\"https:\/\/authjs.dev\/getting-started\/database\">database adapters<\/a>. As before, there are many to choose from. We&#8217;ll use&nbsp;<a href=\"https:\/\/authjs.dev\/getting-started\/adapters\/dynamodb\">DynamoDB<\/a>. Compared to providers, the setup for database adapters is a bit more involved, and a bit more tedious. In order to keep the focus of this post on Auth.js, we won&#8217;t walk through setting up each and every key field, TTL setting, and GSI\u2014to say nothing of AWS credentials if you don&#8217;t have them already. If you&#8217;ve never used Dynamo and are curious, I wrote an introduction&nbsp;<a href=\"https:\/\/adamrackis.dev\/blog\/dynamo-introduction\">here<\/a>. If you&#8217;re not really interested in Dynamo, this section will show you the basics of setting up database adapters, which you can apply to any of the (many) others you might prefer to use.<\/p>\n\n\n\n<p>That said, if you&#8217;re interested in implementing this yourself, the&nbsp;<a href=\"https:\/\/authjs.dev\/getting-started\/adapters\/dynamodb#advanced-usage\">adapter docs<\/a>&nbsp;provide CDK and CloudFormation templates for the Dynamo table you need, or if you want a low-dev-ops solution, it even lists out the keys, TTL and GSI structure&nbsp;<a href=\"https:\/\/authjs.dev\/getting-started\/adapters\/dynamodb#default-schema\">here<\/a>, which is pretty painless to just set up.<\/p>\n\n\n\n<p>We&#8217;ll assume you&#8217;ve got your DynamoDB instance set up, and look at the code to connect it. First, we&#8217;ll install some new libraries<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm i @auth\/dynamodb-adapter @aws-sdk\/lib-dynamodb @aws-sdk\/client-dynamodb<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First, make sure your dynamo table name, as well as your AWS credentials are in environment variables<\/p>\n\n\n\n<p>Now we&#8217;ll go back to our <code>hooks.server.ts<\/code> file, and whip up some boilerplate (which, to be honest, is mostly copied right from the docs).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { GOOGLE_AUTH_CLIENT_ID, GOOGLE_AUTH_SECRET, AMAZON_ACCESS_KEY, AMAZON_SECRET_KEY, DYNAMO_AUTH_TABLE, AUTH_SECRET } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$env\/static\/private\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { DynamoDB, type DynamoDBClientConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@aws-sdk\/client-dynamodb\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { DynamoDBDocument } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@aws-sdk\/lib-dynamodb\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { DynamoDBAdapter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@next-auth\/dynamodb-adapter\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> type { Adapter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth\/core\/adapters\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> dynamoConfig: DynamoDBClientConfig = {\n  <span class=\"hljs-attr\">credentials<\/span>: {\n    <span class=\"hljs-attr\">accessKeyId<\/span>: AMAZON_ACCESS_KEY,\n    <span class=\"hljs-attr\">secretAccessKey<\/span>: AMAZON_SECRET_KEY,\n  },\n\n  <span class=\"hljs-attr\">region<\/span>: <span class=\"hljs-string\">\"us-east-1\"<\/span>,\n};\n\n<span class=\"hljs-keyword\">const<\/span> client = DynamoDBDocument.from(<span class=\"hljs-keyword\">new<\/span> DynamoDB(dynamoConfig), {\n  <span class=\"hljs-attr\">marshallOptions<\/span>: {\n    <span class=\"hljs-attr\">convertEmptyValues<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">removeUndefinedValues<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">convertClassInstanceToMap<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  },\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and now we add our adapter to our auth config:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  adapter: DynamoDBAdapter(client, { <span class=\"hljs-attr\">tableName<\/span>: DYNAMO_AUTH_TABLE }),<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and now, after logging out, and logging back in, we should see some entries in our DynamoDB instance<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"250\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=1024%2C250&#038;ssl=1\" alt=\"\" class=\"wp-image-1777\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=1024%2C250&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=300%2C73&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=768%2C188&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=1536%2C375&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/img11-saving-in-dynamo.jpg?resize=2048%2C500&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Authentication hooks<\/h2>\n\n\n\n<p>The <code>auth-core<\/code> package provides a number of callbacks you can hook into, if you need to do some custom processing.<\/p>\n\n\n\n<p>The&nbsp;<code>signIn<\/code>&nbsp;callback is invoked, predictably, after a successful login. It&#8217;s passed an account object from whatever provider was used, Google in our case. One use case with this callback could be to optionally look up, and sync legacy user metadata you might have stored for your users before switching over to OUath authentication with established providers.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> signIn({ account }) {\n  <span class=\"hljs-keyword\">const<\/span> userSync = <span class=\"hljs-keyword\">await<\/span> getLegacyUserInfo(account.providerAccountId);\n  <span class=\"hljs-keyword\">if<\/span> (userSync) {\n    account.syncdId = userSync.sk;\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>;\n},\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The&nbsp;<code>jwt<\/code>&nbsp;callback gives you the ability to store additional info in the authentication token (you can use this regardless of whether you&#8217;re using a database adapter). It&#8217;s passed the (possibly mutated) account object from the&nbsp;<code>signIn<\/code>&nbsp;callback.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> jwt({ token, account }) {\n  token.userId ??= account?.syncdId || account?.providerAccountId;\n  <span class=\"hljs-keyword\">if<\/span> (account?.syncdId) {\n    token.legacySync = <span class=\"hljs-literal\">true<\/span>;\n  }\n  <span class=\"hljs-keyword\">return<\/span> token;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We&#8217;re setting a single&nbsp;<code>userId<\/code>&nbsp;onto our token that&#8217;s either the syndId we just looked up, or the&nbsp;<code>providerAccountId<\/code>&nbsp;already attached to the provider account. If you&#8217;re curious about the&nbsp;<code>??=<\/code>&nbsp;operator, that&#8217;s the&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Nullish_coalescing_assignment\">nullish coalescing assignment operator<\/a>.<\/p>\n\n\n\n<p>Lastly, the&nbsp;<code>session<\/code>&nbsp;callback gives you an opportunity to shape the session object that&#8217;s returned when your application code calls&nbsp;<code>locals.getSession()<\/code><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> session({ session, user, token }: any) {\n  session.userId = token.userId;\n  <span class=\"hljs-keyword\">if<\/span> (token.legacySync) {\n    session.legacySync = <span class=\"hljs-literal\">true<\/span>;\n  }\n  <span class=\"hljs-keyword\">return<\/span> session;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>now our code could look for the&nbsp;<code>legacySync<\/code>&nbsp;property, to discern that a given login has already sync&#8217;d with a legacy account, and therefore know not to ever prompt the user about this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Extending the types<\/h2>\n\n\n\n<p>Let&#8217;s say we do extand the default session type, like we did above. Let&#8217;s see how we can tell TypeScript about the things we&#8217;re adding. Basically, we need to use a TypeScript feature called interface merging. We essentially re-declare an interface that already exists, add stuff, and then TypeScript does the grunt work of merging (hence the name) the&nbsp;<em>original type<\/em>&nbsp;along with the changes we&#8217;ve made.<\/p>\n\n\n\n<p>Let&#8217;s see it in action. Go to the&nbsp;<code>app.d.ts<\/code>&nbsp;file SvelteKit adds to the root src folder, and add this<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">declare <span class=\"hljs-built_in\">module<\/span> <span class=\"hljs-string\">\"@auth\/core\/types\"<\/span> {\n  interface Session {\n    <span class=\"hljs-attr\">userId<\/span>: string;\n    provider: string;\n    legacySync: boolean;\n  }\n}\n\n<span class=\"hljs-keyword\">export<\/span> {};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We have to put the interface in the right module, and then we add what we need to add.<\/p>\n\n\n\n<p>Note the odd <code>export {};<\/code> at the end. There has to be at least one ESM import or export, so TypeScript treats the file correctly. SvelteKit by default adds this, but make sure it&#8217;s present in your final product.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping up<\/h2>\n\n\n\n<p>We&#8217;ve covered a broad range of topics in this post. We&#8217;ve seen how to set up Auth.js in a SvelteKit project using the&nbsp;<code>@auth\/core<\/code>&nbsp;library. We saw how to set up providers, adapters, and then took a look at various callbacks that allow us to customize our authentication flows.<\/p>\n\n\n\n<p>Best of all, the tools we saw will work with SvelteKit or Next, so if you&#8217;re already an experienced Next user, a lot of this was probably familiar. If not, much of what you saw will be portable to Next if you ever find yourself using that.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;ll look at how to use @auth\/core, add a Google Provider, and get our sessions persisting in DynamoDB. There is a GitHub repo for reference on everything.<\/p>\n","protected":false},"author":21,"featured_media":1779,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[160,161,162],"class_list":["post-1764","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-auth","tag-svelte","tag-sveltekit"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/auth-thumb.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1764","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=1764"}],"version-history":[{"count":16,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1764\/revisions"}],"predecessor-version":[{"id":1926,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1764\/revisions\/1926"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/1779"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=1764"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=1764"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=1764"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}