A thin wrapper around the auth0/nextjs-auth0 SDK, to adapt it to use with Svelte-Kit.
CAUTION: Svelte-Kit is still in beta. It's possible things might break if Svelte-Kit's API changes drastically in the future.
Run pnpm add sveltekit-auth0-unofficial
(or npm add
or yarn add
). You'll also need to use the Node adapter for Svelte-Kit. Edit your svelte.config.js
and make it look something like this:
import node from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
// Node adapter
adapter: node(),
},
};
export default config;
Once you've installed sveltekit-auth0-unofficial
, start by setting up your Auth0 config. The recommended way to do this is to use environment variables; a complete list of environment variables available is at https://auth0.github.io/nextjs-auth0/modules/config.html. Alternately, you could keep your config secrets in a file that you don't check in to git. For example, you could add secrets.js
to your .gitignore
file and then write something like this:
// src/lib/auth0.ts
import { initAuth0 } from 'sveltekit-auth0-unofficial';
import { AUTH0_SECRET } from './secrets';
// In addition to environment variables, can also put Auth0 settings here
const myAuth0Config = {
// Required settings (must specify either here or in environment variables)
secret: AUTH0_SECRET, // Should be a randomly-generated string of at *least* 32 characters.
// clientID: 'my_auth0_client_id',
// clientSecret: AUTH0_CLIENT_SECRET,
// In this example, AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET are passed in via environment variables and not specified here
baseURL: 'http://localhost:3000',
issuerBaseURL: 'https://example.us.auth0.com',
// Optional settings
enableTelemetry: false, // Don't send Auth0 the library version and Node version you're running
routes: {
postLogoutRedirect: '/goodbye',
// Can also specify login and callback routes here; default is /api/auth/login and /api/auth/callback
}
}
// Environment variables and custom config will be merged in initAuth0, and its result should be your module's default export
export default initAuth0(myAuth0Config);
Then either follow the quick-start instructions below, or the manual setup instructions further down.
Simply create a file named src/routes/api/auth/[auth0].ts
and use the handleAuth()
function to handle authentication for you:
// src/routes/api/auth/[auth0].ts
import auth0 from '$lib/auth0';
export function get(req) {
return auth0.handleAuth(req);
}
Note that the name of the route parameter must
be "auth0". The handleAuth()
function will automatically handle routes named login
, logout
, callback
, and me
. If you want to use other names for those routes, follow the manual setup instructions instead.
You'll also want to put the following into src/hooks.ts
:
import type { Handle } from '@sveltejs/kit';
import auth0 from '$lib/auth0';
export const handle: Handle = async ({ request, resolve }) => {
const auth0Session = auth0.getSession(request);
request.locals.auth0Session = auth0Session;
request.locals.isAuthenticated = !!auth0Session?.user;
request.locals.user = auth0Session?.user || {};
const response = await resolve(request);
// You could modify the response here, e.g. by adding custom headers
return response;
};
export function getSession(request) {
const { isAuthenticated, user } = request.locals;
return { isAuthenticated, user }
}
Now you'll have access to the user
object and isAuthenticated
boolean in the export load({ session })
functions on your pages. Additionally, in your endpoint handlers you'll have access to request.locals.auth0Session
which will contain auth0Session.idToken
, auth0Session.accessToken
, and so on. (See https://auth0.github.io/nextjs-auth0/classes/session_session.default.html for more).
You may want to read about the helper functions available now.
Instead of letting handleAuth
define the routes for you, you can use individual functions for each route. For example, let's say you wanted your auth routes to be at the URLs /login
, /logout
, /api/callback
, and /api/me.json
. You'd first need to define those routes in your Auth0 config:
// src/lib/auth0.ts
import { initAuth0 } from '@auth0/nextjs-auth0';
const myAuth0Config = {
routes: {
// Must specify login and callback routes if they differ from the default
login: '/login',
callback: '/api/callback',
// Don't need to specify logout and profile routes
}
}
// Environment variables and custom config will be merged in initAuth0, and its result should be your module's default export
export default initAuth0(myAuth0Config);
And then you could write your routes as follows:
// src/routes/login.ts
import auth0 from '$lib/auth0';
export function get(req) {
return auth0.handleLogin(req);
}
// src/routes/logout.ts
import auth0 from '$lib/auth0';
export function get(req) {
return auth0.handleLogout(req);
}
// src/routes/api/callback.js
import auth0 from '$lib/auth0';
export function get(req) {
return auth0.handleCallback(req);
}
// src/routes/api/me.json.js
import auth0 from '$lib/auth0';
export function get(req) {
return auth0.handleProfile(req);
}
You'll also want to put the following into src/hooks.ts
:
import type { Handle } from '@sveltejs/kit';
import auth0 from '$lib/auth0';
export const handle: Handle = async ({ request, resolve }) => {
const auth0Session = auth0.getSession(request);
request.locals.auth0Session = auth0Session;
request.locals.isAuthenticated = !!auth0Session?.user;
request.locals.user = auth0Session?.user || {};
const response = await resolve(request);
// You could modify the response here, e.g. by adding custom headers
return response;
};
export function getSession(request) {
const { isAuthenticated, user } = request.locals;
return { isAuthenticated, user }
}
Now you'll have access to the user
object and isAuthenticated
boolean in the export load({ session })
functions on your pages. Additionally, in your endpoint handlers you'll have access to request.locals.auth0Session
which will contain auth0Session.idToken
, auth0Session.accessToken
, and so on. (See https://auth0.github.io/nextjs-auth0/classes/session_session.default.html for more).
The Auth0 functions from nextjs-auth0
all take the same optional parameters documented in https://auth0.github.io/nextjs-auth0/. For example, to force a user profile refresh, call auth0.handleProfile(req, { refetch: true });
. Or if you want to add custom data to the Auth0 session cookie, or remove data you don't want stored in the cookie, you can specify an afterCallback
function in the callback handler, like so:
// src/routes/api/auth/callback.js
import auth0 from '$lib/auth0';
const afterCallback = (req, res, session, state) => {
session.user.customProperty = 'foo';
delete session.refreshToken;
return session;
};
export function get(req) {
return auth0.handleCallback(req, { afterCallback });
}
NOTE: The req
and res
objects your afterCallback
function will receive are not real request and response objects. The https://auth0.github.io/nextjs-auth0/ documentation will list their type as NextApiRequest and NextApiResponse, but in fact they are minimal wrapper classes created by the sveltekit-auth0-unofficial
library. These classes wrap around the request data that Svelte-Kit provides to your server-side route code, and simulate the NextApiRequest and NextApiResponse API just enough for nextjs-auth0
to do its work. Do not be surprised if some basic functionality is missing from the req
and res
objects that your afterCallback
function receives. This applies to all the callbacks you might pass to a nextjs-auth0
function: the req
and res
objects your callback receives will have minimal functionality.
To return to a specific page after logging in, you can use a returnTo
parameter.
// src/routes/login.js
import auth0 from '../auth0';
export function get(svelteReq) {
const returnTo = svelteReq.query.get('returnTo');
return auth0.handleLogin(svelteReq, { returnTo });
}
The nextjs-auth0
library defines two helper functions, withPageAuthRequired
and withApiAuthRequired
, for pages and API endpoints respectively. In sveltekit-auth0-unofficial
, these functions have been converted to take arguments more appropriate for Svelte-Kit. The withPageAuthRequired
function takes an optional parameters object, just as in nextjs-auth0
, but where the nextjs-auth0
version of this function allows you to pass in your own getServerSideProps
function, the Svelte-Kit version of withPageAuthRequired
will have you pass in your own load
function instead. The return value of withPageAuthRequired
is suitable for exporting as your page's load
function. E.g.,
<script context="module">
export const load = auth0.withPageAuthRequired()
</script>
withPageAuthRequired
will check whether the session contains a logged in user (detected by whether the session contains an isAuthenticated
boolean that is true); if not, then it will redirect to your login page (as defined in your auth0 config) and return to the current page after the user has logged in. If the session contains a logged in user, then the value of session.user
will be passed into props.user
.
The optional returnTo
parameter is not needed most of the time: withPageAuthRequired
will calculate the return URL based on the current page's path and query parameters, which should be what you need most of the time. But if you need to return to a URL that's not exactly the one that the page was loaded by, then pass a returnTo
parameter (as a string).
The optional load
parameter (which replaces the getServerSideProps
parameter from the nextjs-auth0
library), if specified, should be a standard Svelte-Kit load
function, which can be either synchronous or async. If there is no logged-in user, your load
function will not be called, and withPageAuthRequired
will redirect to the login URL. So your load
function can count on there being a valid user in session.user
. If your load
function returns anything, then the props will be populated with a user
value corresponding to session.user
; but if your load
function returns nothing (because you intend to fall through to a different route), then withPageAuthRequired
will not populate the props and will also return nothing.
Usage example with inner load
function:
<script context="module">
import { browser, dev } from '$app/env';
import auth0 from '$lib/auth0';
export const load = auth0.withPageAuthRequired({ load: async function load(params) {
// Yes, this function doesn't need to be async, but I made it async for the example
return { props: { innerLoadTest: "Yep, inner load got called" }};
// No need to populate `props.user`, as `withPageAuthRequired` will take care of that part
}});
</script>
<script lang="ts">
export let innerLoadTest: string = "Nope, inner load was NOT called";
export let user: any;
</script>
<svelte:head>
<title>Profile</title>
</svelte:head>
<div class="content">
<h1>Your profile</h1>
<p>
Hello, {user.given_name} {user.family_name}. <br/>
Here's the profile picture you chose: <br/>
<!-- svelte-ignore a11y-img-redundant-alt -->
<img src={user.picture} alt='Profile image'>
</p>
<p>
You can <a href="/api/auth/logout">log out</a> if you want.
</p>
<p>
{innerLoadTest}
</p>
</div>
The withApiAuthRequired
function is simpler. It takes an endpoint handler (a get
, post
, etc. function) and wraps it in code that checks whether there's a valid login session. If there is already a valid session in request.locals
(request.locals.isAuthenticated
is true, and request.locals.user
is a non-nullish value) then it will call your endpoint handler. If request.locals
does not contain a valid session, it tries to construct one by calling the getSession
function from nextjs-auth0
, and if the Auth0 function returns a valid session, withApiAuthRequired
will populate request.locals
with auth0Session
, user
and isAuthenticated
values. If there is no login session available, then withApiAuthRequired
will return a 401 result containing the following JSON: { error: 'not_authenticated', description: 'The user does not have an active session or is not authenticated' }
.
The second parameter to withApiAuthRequired
, if passed, should be an object with one of two keys:
unauthHandler
: a request handler that will be called instead of returning a 401 if there is no Auth0 login session (this could, for example, return a "basic" version of a resource, with the "full" version available only to logged-in users, or it could localize the JSON)unauthJson
: the JSON to return in the body of the 401, instead of the default JSON above.If both unauthHandler
and unauthJson
are specified, unauthHandler
will be used and unauthJson
will be ignored.