Skip to content

Commit

Permalink
feat(oidc): dpop inside serviceworker (#1306) (release)
Browse files Browse the repository at this point in the history
* feat(oidc): dpop inside serviceworker

* test

* update

* update

* update

* update
  • Loading branch information
guillaume-chervet committed Feb 22, 2024
1 parent 4a5887f commit 8f3940c
Show file tree
Hide file tree
Showing 19 changed files with 474 additions and 59 deletions.
4 changes: 3 additions & 1 deletion examples/react-oidc-demo/public/OidcTrustedDomains.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions examples/react-oidc-demo/public/staticwebapp.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@
"navigationFallback": {
"rewrite": "index.html",
"exclude": ["*.{svg,png,jpg,gif}","*.{css,scss}","*.js"]
},
"globalHeaders": {
"content-security-policy": "script-src 'self'",
"Access-Control-Allow-Origin": "*",
"X-Frame-Options": "SAMEORIGIN",
"X-Permitted-Cross-Domain-Policies": "none",
"Referrer-Policy":"no-referrer",
"X-Content-Type-Options": "nosniff",
"Permissions-Policy": "autoplay=()"
}
}
8 changes: 4 additions & 4 deletions examples/react-oidc-demo/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import React, {useEffect} from 'react';
import {useNavigate} from "react-router-dom";


/*const createIframeHack =() => {
const createIframeHack =() => {
const iframe = document.createElement('iframe');
const html = '<body>Foo<script>alert("youhou");</script></body>';
iframe.srcdoc = html;
document.body.appendChild(iframe);
}*/
}

export const Home = () => {
const { login, logout, renewTokens, isAuthenticated } = useOidc();
Expand All @@ -18,9 +18,9 @@ export const Home = () => {
navigate("/profile");
};

/*useEffect(() => {
useEffect(() => {
createIframeHack();
}, []);*/
}, []);


return (
Expand Down
2 changes: 1 addition & 1 deletion examples/react-oidc-demo/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default defineConfig({
},
server: {
headers: {
// "Content-Security-Policy": "script-src 'self' 'unsafe-inline';",
//"Content-Security-Policy": "script-src 'unsafe-inline' https://www.google-analitics.com;",
},
},
});
65 changes: 44 additions & 21 deletions packages/oidc-client-service-worker/src/OidcServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
import {extractConfigurationNameFromCodeVerifier, replaceCodeVerifier} from './utils/codeVerifier';
import { normalizeUrl } from './utils/normalizeUrl';
import version from './version';
import {generateJwkAsync, generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
import {getDpopConfiguration} from "./dpop";
import {base64urlOfHashOfASCIIEncodingAsync} from "./crypto";

// @ts-ignore
if (typeof trustedTypes !== 'undefined' && typeof trustedTypes.createPolicy == 'function') {
Expand Down Expand Up @@ -92,6 +95,19 @@ const keepAliveAsync = async (event: FetchEvent) => {
return response;
};

async function generateDpopAsync(originalRequest: Request, currentDatabase:OidcConfig|null, url: string, extrasClaims={} ) {
const headersExtras = serializeHeaders(originalRequest.headers);
if (currentDatabase && currentDatabase.demonstratingProofOfPossessionConfiguration && currentDatabase.demonstratingProofOfPossessionJwkJson) {
const dpopConfiguration = currentDatabase.demonstratingProofOfPossessionConfiguration;
const jwk = currentDatabase.demonstratingProofOfPossessionJwkJson;
headersExtras['dpop'] = await generateJwtDemonstratingProofOfPossessionAsync(self)(dpopConfiguration)(jwk, 'POST', url, extrasClaims);
if(currentDatabase.demonstratingProofOfPossessionNonce != null) {
headersExtras['nonce'] = currentDatabase.demonstratingProofOfPossessionNonce;
}
}
return headersExtras;
}

const handleFetch = async (event: FetchEvent) => {
const originalRequest = event.request;
const url = normalizeUrl(originalRequest.url);
Expand Down Expand Up @@ -176,16 +192,18 @@ const handleFetch = async (event: FetchEvent) => {
if (numberDatabase > 0) {
const maPromesse = new Promise<Response>((resolve, reject) => {
const clonedRequest = originalRequest.clone();
const response = clonedRequest.text().then((actualBody) => {
const response = clonedRequest.text().then(async (actualBody) => {
if (
actualBody.includes(TOKEN.REFRESH_TOKEN) ||
actualBody.includes(TOKEN.ACCESS_TOKEN)
) {
let headers = serializeHeaders(originalRequest.headers);
let newBody = actualBody;
for (let i = 0; i < numberDatabase; i++) {
const currentDb = currentDatabases[i];

if (currentDb && currentDb.tokens != null) {
const claimsExtras = {ath: await base64urlOfHashOfASCIIEncodingAsync(currentDb.tokens.access_token),};
headers = await generateDpopAsync(originalRequest, currentDb, url, claimsExtras);
const keyRefreshToken =
TOKEN.REFRESH_TOKEN + '_' + currentDb.configurationName;
if (actualBody.includes(keyRefreshToken)) {
Expand All @@ -194,6 +212,7 @@ const handleFetch = async (event: FetchEvent) => {
encodeURIComponent(currentDb.tokens.refresh_token as string),
);
currentDatabase = currentDb;

break;
}
const keyAccessToken =
Expand All @@ -208,11 +227,12 @@ const handleFetch = async (event: FetchEvent) => {
}
}
}

const fetchPromise = fetch(originalRequest, {
body: newBody,
method: clonedRequest.method,
headers: {
...serializeHeaders(originalRequest.headers),
...headers,
},
mode: clonedRequest.mode,
cache: clonedRequest.cache,
Expand Down Expand Up @@ -254,12 +274,14 @@ const handleFetch = async (event: FetchEvent) => {
currentDatabase.codeVerifier,
);
}


const headersExtras = await generateDpopAsync(originalRequest, currentDatabase, url);

return fetch(originalRequest, {
body: newBody,
method: clonedRequest.method,
headers: {
...serializeHeaders(originalRequest.headers),
...headersExtras,
},
mode: clonedRequest.mode,
cache: clonedRequest.cache,
Expand Down Expand Up @@ -301,7 +323,7 @@ const handleFetch = async (event: FetchEvent) => {
}
};

const handleMessage = (event: ExtendableMessageEvent) => {
const handleMessage = async (event: ExtendableMessageEvent) => {
const port = event.ports[0];
const data = event.data as MessageEventData;
if (event.data.type === 'claim') {
Expand Down Expand Up @@ -340,18 +362,23 @@ const handleMessage = (event: ExtendableMessageEvent) => {
convertAllRequestsToCorsExceptNavigate ?? false,
demonstratingProofOfPossessionNonce: null,
demonstratingProofOfPossessionJwkJson: null,
demonstratingProofOfPossessionConfiguration: null,
};
currentDatabase = database[configurationName];

if (!trustedDomains[configurationName]) {
trustedDomains[configurationName] = [];
}
}

switch (data.type) {
case 'clear':
currentDatabase.tokens = null;
currentDatabase.state = null;
currentDatabase.codeVerifier = null;
currentDatabase.demonstratingProofOfPossessionNonce = null;
currentDatabase.demonstratingProofOfPossessionJwkJson = null;
currentDatabase.demonstratingProofOfPossessionConfiguration = null;
currentDatabase.status = data.data.status;
port.postMessage({ configurationName });
return;
Expand All @@ -372,6 +399,17 @@ const handleMessage = (event: ExtendableMessageEvent) => {
currentDatabase.oidcServerConfiguration = oidcServerConfiguration;
currentDatabase.oidcConfiguration = data.data.oidcConfiguration;

if(currentDatabase.demonstratingProofOfPossessionConfiguration == null ){
const demonstratingProofOfPossessionConfiguration = getDpopConfiguration(trustedDomains[configurationName]);
if(demonstratingProofOfPossessionConfiguration != null){
if(currentDatabase.oidcConfiguration.demonstrating_proof_of_possession){
console.warn("In service worker, demonstrating_proof_of_possession must be configured from trustedDomains file")
}
currentDatabase.demonstratingProofOfPossessionConfiguration = demonstratingProofOfPossessionConfiguration;
currentDatabase.demonstratingProofOfPossessionJwkJson = await generateJwkAsync(self)(demonstratingProofOfPossessionConfiguration.generateKeyAlgorithm);
}
}

if (!currentDatabase.tokens) {
port.postMessage({
tokens: null,
Expand Down Expand Up @@ -421,21 +459,6 @@ const handleMessage = (event: ExtendableMessageEvent) => {
});
return;
}
case 'setDemonstratingProofOfPossessionJwk': {
currentDatabase.demonstratingProofOfPossessionJwkJson =
data.data.demonstratingProofOfPossessionJwkJson;
port.postMessage({ configurationName });
return;
}
case 'getDemonstratingProofOfPossessionJwk': {
const demonstratingProofOfPossessionJwkJson =
currentDatabase.demonstratingProofOfPossessionJwkJson;
port.postMessage({
configurationName,
demonstratingProofOfPossessionJwkJson,
});
return;
}
case 'setState': {
currentDatabase.state = data.data.state;
port.postMessage({ configurationName });
Expand Down
20 changes: 20 additions & 0 deletions packages/oidc-client-service-worker/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {uint8ToUrlBase64} from "./jwt";


export function textEncodeLite(str: string) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);

for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}

export function base64urlOfHashOfASCIIEncodingAsync(code: string):Promise<string> {
return new Promise((resolve, reject) => {
crypto.subtle.digest('SHA-256', textEncodeLite(code)).then(buffer => {
return resolve(uint8ToUrlBase64(new Uint8Array(buffer)));
}, error => reject(error));
});
}
22 changes: 22 additions & 0 deletions packages/oidc-client-service-worker/src/dpop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Domain, DomainDetails} from "./types.js";
import {defaultDemonstratingProofOfPossessionConfiguration} from "./jwt";

const isDpop= (trustedDomain: Domain[] | DomainDetails) : boolean => {
if (Array.isArray(trustedDomain)) {
return false;
}
return trustedDomain.demonstratingProofOfPossession ?? false;
}

export const getDpopConfiguration = (trustedDomain: Domain[] | DomainDetails) => {

if(!isDpop(trustedDomain)) {
return null;
}

if (Array.isArray(trustedDomain)) {
return null;
}

return trustedDomain.demonstratingProofOfPossessionConfiguration ?? defaultDemonstratingProofOfPossessionConfiguration;
}
Loading

0 comments on commit 8f3940c

Please sign in to comment.