This project aims to integrate OpenID confidential flow using Sveltekit. Once login is complete, Navigation to protected pages of app don't require a request to Authorization Server (Keycloak in our instance). Sveltekit hooks take care of :
npm install sveltekit-oidc * Silent Refresh Workflow
* Validating the client access_token validity
* Renewing the token in case of token expiry
* Offline Auth server error handling
* Setting valid user information ( access_token, refresh_token, userid etc. ) in form of cookies
* Populating session variable with user information
* If Auth server has redirected to application, creates a backchannel machine to machine request for exchanging auth_code with access_token.
When the client side kicks in, it:
* Checks for user and Auth server information in session variable
* In case, no user is found or some error has occured on server-side, populate AuthStore with proper messages
* Provides Login, Logout functionality
* Initiates a front channel Authorization flow, in case of protected component via Sveletkit Load method.
* Logout in one browser tab initiates automatic logout from all tabs.
* Prompt on all browser tabs and Page reloading on User Login.
Complete JWT Implementation based on Hasura Blog on BEST Practices for JWT AUTH
npm i sveltekit-oidc
##
``ts`
VITE_OIDC_ISSUER="http://localhost:28080/auth/realms/hasura"
VITE_OIDC_CLIENT_ID="hasura-app"
VITE_OIDC_CLIENT_SECRET="1439e34f-343e-4f71-bbc7-cc602dced84a"
VITE_OIDC_REDIRECT_URI="http://localhost:3000"
VITE_OIDC_POST_LOGOUT_REDIRECT_URI="http://localhost:3000"
VITE_OIDC_CLIENT_SCOPE="openid profile email hasura-claims"
VITE_OIDC_TOKEN_REFRESH_MAX_RETRIES="5"
VITE_REFRESH_TOKEN_ENDPOINT="/auth/refresh_token"
VITE_REFRESH_PAGE_ON_SESSION_TIMEOUT=true
`ts`
interface ImportMetaEnv {
VITE_OIDC_ISSUER: string;
VITE_OIDC_CLIENT_ID: string;
VITE_OIDC_CLIENT_SECRET: string;
VITE_OIDC_REDIRECT_URI: string;
VITE_OIDC_POST_LOGOUT_REDIRECT_URI: string;
VITE_OIDC_CLIENT_SCOPE: string;
VITE_OIDC_TOKEN_REFRESH_MAX_RETRIES: number;
VITE_REFRESH_TOKEN_ENDPOINT: string;
VITE_REFRESH_PAGE_ON_SESSION_TIMEOUT: boolean;
}`$3
Create a refresh_token endpoint as set in .env file (VITE_REFRESH_TOKEN_ENDPOINT) we have set /auth/refresh_token.
As such, create file src/routes/auth/refresh_token.ts ts
import { renewOIDCToken } from 'sveltekit-oidc';
import type { Locals } from 'sveltekit-oidc/types';
import type { RequestHandler } from '@sveltejs/kit';
const oidcBaseUrl = ${import.meta.env.VITE_OIDC_ISSUER}/protocol/openid-connect;${import.meta.env.VITE_OIDC_CLIENT_ID}
const clientId = ;
const clientSecret = process.env.VITE_OIDC_CLIENT_SECRET || import.meta.env.VITE_OIDC_CLIENT_SECRET;
/**
* @type {import('@sveltejs/kit').RequestHandler}
*/
export const post: RequestHandler
const data = await renewOIDCToken(request.body.get('refresh_token'), oidcBaseUrl, clientId, clientSecret);
const response = {
body: {
...data
}
};
return response;
}
``$3
ts
import type { Handle, GetSession } from '@sveltejs/kit';
import {
userDetailsGenerator,
getUserSession
} from 'sveltekit-oidc';
import type { Locals } from 'sveltekit-oidc/types';
import type { ServerRequest } from '@sveltejs/kit/types/hooks';
const clientSecret = process.env.VITE_OIDC_CLIENT_SECRET || import.meta.env.VITE_OIDC_CLIENT_SECRET;
export const handle: Handle
// Initialization part
const userGen = userDetailsGenerator(request, clientSecret);
const { value, done } = await userGen.next();
if ( done ) {
const response = value;
return response;
}
// Set Cookie attributes
request.locals.cookieAttributes = 'Path=/; HttpOnly; SameSite=Lax;';
// Your code here -----------
if (request.query.has('_method')) {
request.method = request.query.get('_method').toUpperCase();
}
// Handle resolve
const response = await resolve(request);
// After your code ends, Populate response headers with Auth Info
// wrap up response by over-riding headers and status
if ( response?.status !== 404 ) {
const extraResponse = (await userGen.next(request)).value;
const { Location, ...restHeaders } = extraResponse.headers;
// SSR Redirection
if ( extraResponse.status === 302 && Location ) {
response.status = extraResponse.status
response.headers['Location'] = Location;
}
response.headers = {...response.headers, ...restHeaders};
}
// Return response back
return response;
};
/* @type {import('@sveltejs/kit').GetSession} /
export const getSession: GetSession = async (request: ServerRequest
const userSession = await getUserSession(request, clientSecret);
return userSession;
}
`
html
issuer={import.meta.env.VITE_OIDC_ISSUER}
client_id={import.meta.env.VITE_OIDC_CLIENT_ID}
scope={import.meta.env.VITE_OIDC_CLIENT_SCOPE}
redirect_uri={import.meta.env.VITE_OIDC_REDIRECT_URI}
post_logout_redirect_uri={import.meta.env.VITE_OIDC_POST_LOGOUT_REDIRECT_URI}
refresh_token_endpoint={import.meta.env.VITE_REFRESH_TOKEN_ENDPOINT}
refresh_page_on_session_timeout={import.meta.env.VITE_REFRESH_PAGE_ON_SESSION_TIMEOUT}
>
`
$3
`html
{#if $isAuthenticated}
User is authenticated
{:else}
Login
{/if}
`
$3
`html
`
Application Screenshots
$3
!Login Page$3
!Keycloak Auth$3
!Index page with JWTDeveloping
Once you've created a project and installed dependencies with
npm install (or pnpm install or yarn), start a development server:`bash
npm run devor start the server and open the app in a new browser tab
npm run dev -- --open
`Building
`bash
npm run build
``