@byu-oit-sdk/svelte v0.2.2
@byu-oit-sdk/svelte
Requirements:
- Node.js 18+
- or Node.js 10+ with fetch and crypto polyfills
- npm v9+
Installing
npm install @byu-oit-sdk/svelte
Introduction
Use this module to configure SvelteKit websites that need to implement the OAuth 2.0 Authorization Code grant type. This package requires you to also implement @byu-oit-sdk/session-svelte. See usage examples below.
This module assumes that the access token returned after the authorization code exchange will be a jwt that it can decode to extract user information. This behavior is configurable by passing in the
userInfoCallback
function into the plugin configuration. AbyuOitUserInfo
function is also exported which can be used to override the default for websites using BYU's API manager.
Options
In addition to the options below, any AuthorizationCodeProvider options may also be passed into the configuration.
Option | Type | Default | Description |
---|---|---|---|
logIn | string | /auth/signin | The path which redirects the caller to the authorization url to sign in. |
logInRedirect | string | /auth/user | The path to redirect the caller to after signing in. |
logOut | string | /auth/signout | The path to call to clear a user's session and revoke the access token. |
logOutRedirect | string | / | The path to redirect the caller to after signing out. |
errorRedirect | string | /error | The path to redirect the caller to after an error is encountered during the authorization flow. |
userInfoCookieName | string | user_info | The name of the cookie containing the user information parsed from the token. |
sessionIdCookieName | string | sessionId | The name of the cookie where the users session id can be found. If overriding the default, this value must be identical to the value passed in to @byu-oit-sdk/session-svelte |
userInfoCallback | function | (decodes the token) | This is the function that can be provided to tell this package how to decode the token. If you are using BYU's API manager, you can use the exported byuOitUserInfo function. See below. |
clientId | string | - | The client identifier issued to the client during the registration process. |
clientSecret | string | - | The client secret of the account being used. Some Oauth providers (such as BYU's API manager) will not require this value, but others, like Okta, will. |
redirectUri | string | - | The redirection endpoint that the authorization server should redirect to after authenticating the resource owner. This can be a relative URL (e.g. /callback ) or absolute (e.g. https://somesite.byu.edu/auth/callback ) |
discoveryEndpoint | string | - | Used to configure where the user will be sent to sign in. |
scope | string | - | An Oauth property for determining access to data (docs). Set this to open_id when using BYU's API manager. |
Note that any of the parameters of type string can also be defined through environment variables with the BYUOIT prefix.
In the code examples below, if any of the required parameters are not provided it is because environment variables were used instead.
Signing In
- For a user to log in, the browser should direct the user to the
logIn
route. - After the user logs in, the server will redirect the user to the location provided in the
redirect
query parameter from the login step, or falls back to thelogInRedirect
option passed into the configuration. - If the user encounters an error, they will be redirected to
errorRedirect
and an error message will be displayed in the query parameters of the url.
Signing Out
- For a user to log out, the browser should direct the user to the
logOut
route. - When the user logs out, their session is cleared but the token is not revoked.
- After logging out, they will be redirected to the
logOutRedirect
route. - If the user encounters an error, they will be redirected to
errorRedirect
and an error message will be displayed in the query parameters of the url.
Usage
There are two functions exported by this package that must be implemented, and there are others that may be implemented to achieve additional functionality.
Install hooks with AuthorizationCodeFlow()
The only piece that absolutely must be implemented is the AuthorizationCodeFlow
function which exports a SvelteKit handle
function that sets up a series of hooks that handle log-in, log-out, and other authentication functionality.
That handle function should be exported as handle
through a sequence()
call from your hooks.server.ts
file so that you can have the handle from @byu-oit-sdk/session-svelte
be executed first. See the example below:
import { SessionHandler } from '@byu-oit-sdk/session-svelte'
import { AuthorizationCodeFlow } from '@byu-oit-sdk/svelte'
import { sequence } from '@sveltejs/kit/hooks'
import type { Handle } from '@sveltejs/kit'
import env from 'env-var'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoSessionStore } from '@byu-oit-sdk/session-dynamo'
export const handle = await (async () => {
// this file runs (twice!) when running `vite build`, so if we are building, skip this code block.
// See https://github.com/sveltejs/kit/issues/8795
if (!building) {
return sequence()
}
// this is one example of how you could dynamically switch between using dynamo and in-memory stores for local and production use.
const isProduction = env.get('NODE_ENV').default('development').asEnum(['production', 'development']) === 'production'
let store // if store is undefined (e.g. for local development), an in-memory store will be used
if (isProduction) {
const client = new DynamoDBClient({
region: env.get('AWS_REGION').required().asString()
})
store = new DynamoSessionStore({ client, tableName: 'sessions' })
}
const sessionHandle = await SessionHandler({ store })
// the SessionHandler handle must be executed before the AuthorizationCodeFlow handle
return sequence(sessionHandle, AuthorizationCodeFlow({
redirectUri: '/callback'
}))
})()
Oauth data, including information from the decoded token, will now be stored in event.locals.session
in server-side
SvelteKit functions. For example, here is how you could access it in a request handler in a +server.ts
file:
import type {RequestHandler, RequestEvent} from './$types'
export const GET: RequestHandler = async function (event: RequestEvent) {
const session = event.locals.session.data ?? {}
if (!session.token.accessToken || !session?.user?.user_id)
return new Response(JSON.stringify({error: 'No access token'}), {status: 401})
}
const accessToken: string = session.token.accessToken
/* make an api call using the access token */
return new Response(/* add api call response data here */)
}
Note that the above example assumes that the id token contains a user_id property.
Access SessionHandler Information with topLevelLoad()
In addition to implementing the AuthorizationCodeFlow
function in the hooks.server.ts
, you should also export
topLevelLoad()
as load()
from your top-level +layout.server.ts
file (i.e. src/routes/+layout.server.ts
). This
function attaches information obtained from the token (i.e. user name and id) to your pagedata so that you can access it
easily. SvelteKit is expecting a load()
function to be exported from this file, so do not call it anything else.
export { topLevelLoad as load } from '@byu-oit-sdk/svelte'
const { topLevelLoad } = require('@byu-oit-sdk/svelte')
exports.load = topLevelLoad
If you would like to implement your own load functionality in that file, you can define a load() function that executes the imported load function, as shown below:
import { topLevelLoad } from '@byu-oit-sdk/svelte'
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = (event) => {
const sessionLoadResults = topLevelLoad(event)
/* perform custom load functionality */
const foo = 'bar'
return { foo /* additional pagedata */, ...sessionLoadResults }
}
const { topLevelLoad } = require('@byu-oit-sdk/svelte')
exports.load = (event) => {
const sessionLoadResults = topLevelLoad(event)
/* perform custom load functionality */
const foo = 'bar'
return { foo /* additional pagedata */, ...sessionLoadResults }
}
Then, session data can be accessed in your svelte files. For example, a +layout.svelte
needing to access your display_name
might do something like the following:
<script lang="ts">
import type { PageData } from './$types'
export let data: PageData
</script>
{#if data.session.user}
<span slot="user-name">{data.session.user.display_name}</span>
{/if}
Note that this example depends on the id token including a display_name property.
Protect Sensitive Pages with Required or Automatic Authentication
In order to restrict access to certain pages (requiring users to log-in first), we recommend creating a
layout group via a folder whose name is wrapped
in parentheses (e.g. (authenticated)
) so that all the routes that require the user to
be logged-in can be stored in that folder. The parenthesis tell SvelteKit not to use that folder name in the actual
path of the route (e.g. /(authenticated)/foo/bar
is accessed by navigating to /foo/bar
).
Create a +layout.server.ts
file at the base of the directory for your protected routes, and in its exported load
function check if the user has logged in. If not, then either throw an error, or automatically redirect the user to the
login screen with the getLoginUrl
function which is added to event.locals
by AuthorizationCodeFlow.
import type { LayoutServerLoad } from './$types'
import { redirect } from '@sveltejs/kit'
export const load: LayoutServerLoad = async ({ locals }) => {
if (locals.session.data.token == null) {
redirect(302, locals.getLoginUrl('/return_to_me_after_login'))
}
}
If You Are Using BYUs API Manager...
If you are using calling APIs through BYU's API manager, the default behavior of this package may not work because
BYU doesn't use id tokens by default. However, this package exports a byuOitUserInfo
function for this purpose:
import { SessionHandler } from '@byu-oit-sdk/session-svelte'
import { AuthorizationCodeFlow, byuOitUserInfo } from '@byu-oit-sdk/svelte'
import { sequence } from '@sveltejs/kit/hooks'
import type { Handle } from '@sveltejs/kit'
import env from 'env-var'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoSessionStore } from '@byu-oit-sdk/session-dynamo'
export const handle = await (async () => {
// this file runs (twice!) when running `vite build`, so if we are building, skip this code block.
// See https://github.com/sveltejs/kit/issues/8795
if (!building) {
return sequence()
}
// this is one example of how you could dynamically switch between using dynamo and in-memory stores for local and production use.
const isProduction = env.get('NODE_ENV').default('development').asEnum(['production', 'development']) === 'production'
let store // if store is undefined (e.g. for local development), an in-memory store will be used
if (isProduction) {
const client = new DynamoDBClient({
region: env.get('AWS_REGION').required().asString()
})
store = new DynamoSessionStore({ client, tableName: 'sessions' })
}
const sessionHandle = await SessionHandler({ store })
// the SessionHandler handle must be executed before the AuthorizationCodeFlow handle
return sequence(sessionHandle, AuthorizationCodeFlow({
redirectUri: '/callback',
userInfoCallback: byuOitUserInfo
}))
})
Make sure to set your BYU_OIT_SCOPE
environment variable to openid
when using BYU's API manager.
Logging in and out
This package sets up two ways for users to log in and out. The main way is to have a link that sends the user to the value
passed in as logIn
or logOut
in the AuthorizationCodeFlow({})
function (the default is /auth/signin
and
/auth/signout
, respectively). The user could even manually navigate to those routes in their browser.
<a href="/auth/signout">Sign Out</a>
<a href="/auth/signin">Sign In</a>
If you want to redirect the user to login or logout pages while running server-side code, getLoginUrl
and
getLogoutUrl
functions are added to event.locals
by AuthorizationCodeFlow. These functions can be accessed in
server-side load()
functions in +page.server.ts
or +layout.server.ts
files.
import type { LayoutServerLoad } from './$types'
import { error, redirect } from '@sveltejs/kit'
export const load: LayoutServerLoad = async (event) => {
if (event.locals.session == null) {
error(500, 'No session found')
}
if (event.locals.session.data.token == null) {
redirect(302, event.locals.getLoginUrl('/return_to_me_after_login'))
}
}
Note on unit tests
The unit tests for this package are run via ava using the Typescript importer tsimp
and the Typescript runtime tsx
.
- For Node.js versions v18.19 and higher, the
--import=tsimp
node argument is included to address an issue where running unit tests results in an error related to package path exports (ERR_PACKAGE_PATH_NOT_EXPORTED). - Conversely, for Node.js versions v18.18 and lower, the
--loader=tsx
node argument is necessary to prevent a TypeError (ERR_UNKNOWN_FILE_EXTENSION) caused by unrecognized file extensions during unit test execution.
11 days ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
8 months ago
8 months ago
8 months ago
8 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago