1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
import { env as env_priv } from '$env/dynamic/private';
import { env } from '$env/dynamic/public';
import * as client from 'openid-client';
import oncePromise from './oncePromise';
import type { Cookies } from '@sveltejs/kit';
const server = new URL(env.PUBLIC_AUTH_KEYCLOAK_ISSUER);
const clientId = env_priv.PRIVATE_AUTH_KEYCLOAK_ID;
const clientSecret = env_priv.PRIVATE_AUTH_KEYCLOAK_SECRET;
const redirectPath = '/login/callback';
// Only trigger discovery on first client.discovery (resetting the function after a failed discovery)
export const getConfig = oncePromise(() =>
client.discovery(server, clientId, clientSecret).then((config) => {
client.useJwtResponseMode(config);
return config;
})
);
const codeVerifier = client.randomPKCECodeVerifier();
export const getAuthorizeUrl = async (
currentUrl: URL | string,
scope: string[]
) => {
if (!scope.includes('openid')) scope.unshift('openid');
// do same for `email` maybe?
const config = await getConfig();
const redirectUri = new URL(redirectPath, currentUrl);
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
const codeChallengeMethod = 'S256';
let nonce: string | undefined = undefined;
// redirect user to as.authorization_endpoint
let parameters: Record<string, string> = {
redirect_uri: redirectUri.href,
scope: scope.join(' '),
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
};
/**
* We cannot be sure the AS supports PKCE so we're going to use nonce too. Use
* of PKCE is backwards compatible even if the AS doesn't support it which is
* why we're using it regardless.
*/
if (!config.serverMetadata().supportsPKCE()) {
nonce = client.randomNonce();
parameters.nonce = nonce;
}
const redirectTo = client.buildAuthorizationUrl(config, parameters);
return {
/** Defined if PKCE isnt supported */
nonce,
/** Redirect Target URL */
redirectTo,
/** Where we get the user back on */
returnURI: redirectUri,
};
};
/** Throws on failure */
export const authorizeNewSession = async (
currentUrl: URL,
nonce: string | undefined
) => {
const config = await getConfig();
let tokens = await client.authorizationCodeGrant(config, currentUrl, {
pkceCodeVerifier: codeVerifier,
expectedNonce: nonce,
idTokenExpected: true,
});
return tokens;
};
export const unsetCookies = (cookies: Cookies) => {
for (const v of [
'oid__access_token',
'oid__refresh_token',
'oid__token_type',
'oid__expires_at',
'oid__scopes',
])
if (cookies.get(v)) cookies.delete(v, { path: '/' });
};
export const setCookies = (
cookies: Cookies,
tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers
) => {
for (const [k, v] of Object.entries({
oid__access_token: tokens.access_token,
oid__refresh_token: tokens.refresh_token,
oid__token_type: tokens.token_type,
oid__expires_at: '' + (Date.now() + (tokens.expiresIn() ?? 0) * 1000),
oid__scopes: tokens.scope,
}))
if (v)
cookies.set(k, v, {
path: '/',
secure: true,
httpOnly: true,
sameSite: true,
});
};
|