mirror of
https://github.com/TECHNOFAB11/svelte-oidc.git
synced 2026-02-02 09:25:09 +01:00
feat: oidc-client.js based Svelte OidcComponent
follows a pattern similar to @dopry/svelte-auth0, but uses the more standards compliant oidc-client.js library.
This commit is contained in:
commit
4fd62abe31
25 changed files with 7069 additions and 0 deletions
39
src/App.svelte
Normal file
39
src/App.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<script>
|
||||
import {
|
||||
OidcContext,
|
||||
authError,
|
||||
idToken,
|
||||
accessToken,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
login,
|
||||
logout,
|
||||
userInfo,
|
||||
} from './components/components.module.js';
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<OidcContext
|
||||
issuer="process.env.OIDC_ISSUER"
|
||||
client_id="process.env.OIDC_CLIENT_ID"
|
||||
redirect_uri="process.env.OIDC_REDIRECT_URI"
|
||||
post_logout_redirect_uri="process.env.OIDC_POST_LOGOUT_REDIRECT_URI"
|
||||
>
|
||||
|
||||
<button class="btn" on:click|preventDefault='{() => login() }'>Login</button>
|
||||
<button class="btn" on:click|preventDefault='{() => logout() }'>Logout</button>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th style="width: 20%;">store</th><th style="width: 80%;">value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>isLoading</td><td>{$isLoading}</td></tr>
|
||||
<tr><td>isAuthenticated</td><td>{$isAuthenticated}</td></tr>
|
||||
<tr><td>accessToken</td><td>{$accessToken}</td></tr>
|
||||
<tr><td>idToken</td><td style="word-break: break-all;">{$idToken}</td></tr>
|
||||
<tr><td>userInfo</td><td><pre>{JSON.stringify($userInfo, null, 2)}<pre></td></tr>
|
||||
<tr><td>authError</td><td>{$authError}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</OidcContext>
|
||||
</div>
|
||||
111
src/components/OidcContext.svelte
Normal file
111
src/components/OidcContext.svelte
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<script>
|
||||
import oidcClient from 'oidc-client';
|
||||
const { UserManager } = oidcClient;
|
||||
import { onMount, onDestroy, setContext, getContext } from 'svelte';
|
||||
import {
|
||||
OIDC_CONTEXT_REDIRECT_URI,
|
||||
OIDC_CONTEXT_CLIENT_PROMISE,
|
||||
OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI,
|
||||
idToken,
|
||||
accessToken,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
authError,
|
||||
userInfo
|
||||
} from './oidc';
|
||||
|
||||
// props.
|
||||
export let issuer;
|
||||
export let client_id;
|
||||
export let redirect_uri;
|
||||
export let post_logout_redirect_uri;
|
||||
|
||||
setContext(OIDC_CONTEXT_REDIRECT_URI, redirect_uri);
|
||||
setContext(OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI, post_logout_redirect_uri);
|
||||
|
||||
// getContext doesn't seem to return a value in OnMount, so we'll pass the oidcPromise around by reference.
|
||||
const settings = {
|
||||
authority: issuer,
|
||||
client_id,
|
||||
response_type: 'id_token token',
|
||||
redirect_uri,
|
||||
post_logout_redirect_uri,
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
automaticSilentRenew: true,
|
||||
};
|
||||
|
||||
if (issuer.includes('auth0.com')) {
|
||||
settings.metadata = {
|
||||
// added to overcome missing value in auth0 .well-known/openid-configuration
|
||||
// see: https://github.com/IdentityModel/oidc-client-js/issues/1067
|
||||
// see: https://github.com/IdentityModel/oidc-client-js/pull/1068
|
||||
end_session_endpoint: `process.env.OIDC_ISSUER/v2/logout?client_id=process.env.OIDC_CLIENT_ID`,
|
||||
};
|
||||
}
|
||||
const userManager = new UserManager(settings);
|
||||
userManager.events.addUserLoaded(function () {
|
||||
const user = userManager.getUser();
|
||||
accessToken.set(user.access_token);
|
||||
idToken.set(user.id_token);
|
||||
userInfo.set(user.profile);
|
||||
});
|
||||
|
||||
userManager.events.addUserUnloaded(function () {
|
||||
idToken.set('');
|
||||
accessToken.set('');
|
||||
});
|
||||
|
||||
userManager.events.addSilentRenewError(function (e) {
|
||||
authError.set(`silentRenewError: ${e.message}`);
|
||||
});
|
||||
|
||||
let oidcPromise = Promise.resolve(userManager);
|
||||
|
||||
setContext(OIDC_CONTEXT_CLIENT_PROMISE, oidcPromise);
|
||||
|
||||
|
||||
async function handleOnMount() {
|
||||
// on run onMount after oidc
|
||||
const oidc = await oidcPromise;
|
||||
|
||||
// Not all browsers support this, please program defensively!
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Check if something went wrong during login redirect
|
||||
// and extract the error message
|
||||
if (params.has('error')) {
|
||||
authError.set(new Error(params.get('error_description')));
|
||||
}
|
||||
|
||||
// if code then login success
|
||||
if (params.has('code')) {
|
||||
// handle the redirect response.
|
||||
const response = await oidc.signinRedirectCallback();
|
||||
let state = (response && response.state) || {}
|
||||
// Can be smart here and redirect to original path instead of root
|
||||
const url = state && state.targetUrl ? state.targetUrl : window.location.pathname;
|
||||
state = { ...state, isRedirectCallback: true };
|
||||
|
||||
// redirect to the last page we were on when login was configured if it was passed.
|
||||
history.replaceState(state, "", url);
|
||||
// location.href = url;
|
||||
// clear errors on login.
|
||||
authError.set(null);
|
||||
}
|
||||
|
||||
const user = await oidc.getUser();
|
||||
isAuthenticated.set(!!user);
|
||||
accessToken.set(user.access_token);
|
||||
idToken.set(user.id_token);
|
||||
userInfo.set(user.profile);
|
||||
isLoading.set(false);
|
||||
}
|
||||
async function handleOnDestroy() {}
|
||||
|
||||
onMount(handleOnMount);
|
||||
onDestroy(handleOnDestroy);
|
||||
</script>
|
||||
|
||||
|
||||
<slot></slot>
|
||||
3
src/components/components.module.js
Normal file
3
src/components/components.module.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './oidc'
|
||||
export { default as OidcContext } from './OidcContext.svelte';
|
||||
|
||||
62
src/components/oidc.js
Normal file
62
src/components/oidc.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
/**
|
||||
* Stores
|
||||
*/
|
||||
export const isLoading = writable(true);
|
||||
export const isAuthenticated = writable(false);
|
||||
export const accessToken = writable('');
|
||||
export const idToken = writable('');
|
||||
export const userInfo = writable({});
|
||||
export const authError = writable(null);
|
||||
|
||||
/**
|
||||
* Context Keys
|
||||
*
|
||||
* using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has
|
||||
* referential equality to itself, i.e. {} !== {} whereas "x" === "x"), even when you have multiple different contexts
|
||||
* operating across many component layers.
|
||||
*/
|
||||
export const OIDC_CONTEXT_CLIENT_PROMISE = {};
|
||||
export const OIDC_CONTEXT_REDIRECT_URI = {};
|
||||
export const OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI = {};
|
||||
|
||||
/**
|
||||
* Refresh the accessToken store.
|
||||
*/
|
||||
export async function refreshToken() {
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const token = await oidc.signinSilent();
|
||||
accessToken.set(token.accessToken);
|
||||
idToken.set(token.idToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate Register/Login flow.
|
||||
*
|
||||
* @param {boolean} preserveRoute - store current location so callback handler will navigate back to it.
|
||||
* @param {string} callback_url - explicit path to use for the callback.
|
||||
*/
|
||||
export async function login(preserveRoute = true, callback_url = null) {
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const redirect_uri = callback_url || getContext(OIDC_CONTEXT_REDIRECT_URI) || window.location.href;
|
||||
|
||||
// try to keep the user on the same page from which they triggered login. If set to false should typically
|
||||
// cause redirect to /.
|
||||
const appState = (preserveRoute) ? { pathname: window.location.pathname, search: window.location.search } : {}
|
||||
await oidc.signinRedirect({ redirect_uri, appState });
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out the current user.
|
||||
*
|
||||
* @param {string} logout_url - specify the url to return to after login.
|
||||
*/
|
||||
export async function logout(logout_url = null) {
|
||||
// getContext(OIDC_CONTEXT_CLIENT_PROMISE) returns a promise.
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const returnTo = logout_url || getContext(OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI) || window.location.href;
|
||||
accessToken.set('');
|
||||
oidc.signoutRedirect({ returnTo });
|
||||
}
|
||||
8
src/main.js
Normal file
8
src/main.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import App from './App.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.body,
|
||||
props: {},
|
||||
});
|
||||
|
||||
export default app;
|
||||
Loading…
Add table
Add a link
Reference in a new issue