Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions 01-Login/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ <h2 class="user-name"></h2>
<p class="lead text-muted user-email"></p>
</div>
</div>

<div class="row">
<button id="connect-account">Connect Account</button>
</div>
<div class="row">
<pre class="rounded">
<code id="profile-data" class="json"></code></pre>
Expand All @@ -283,7 +285,7 @@ <h2 class="user-name"></h2>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.0/auth0-spa-js.production.js"></script>
<script src="js/auth0-spa-js.development-with-mrrt.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
<script src="js/ui.js"></script>
<script src="js/app.js"></script>
Expand Down
123 changes: 120 additions & 3 deletions 01-Login/public/js/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,110 @@
// The Auth0 client, initialized in configureClient()
let auth0Client = null;

const urlEncodeB64 = input => {
const b64Chars = {
"+": "-",
"/": "_",
"=": ""
};
return input.replace(/[+/=]/g, (m => b64Chars[m]));
};
const bufferToBase64UrlEncoded = input => {
const ie11SafeInput = new Uint8Array(input);
return urlEncodeB64(window.btoa(String.fromCharCode(...Array.from(ie11SafeInput))));
};

const createRandomString = () => {
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.";
let random = "";
const randomValues = Array.from(window.crypto.getRandomValues(new Uint8Array(43)));
randomValues.forEach((v => random += charset[v % charset.length]));
return random;
};

const sha256 = async (s) => {
return window.crypto.subtle.digest({
name: "SHA-256"
}, (new TextEncoder).encode(s));
};

const connectAccount = async () => {
// Get My Account access token with MRRT
const at = await auth0Client.getTokenSilently({ authorizationParams: { audience: 'https://adam.local.dev.auth0.com/me/', scope: 'create:me:connected_accounts' }});

const connection = "oidc-idp";
const state = btoa(createRandomString());
const code_verifier = createRandomString();
const code_challengeBuffer = await sha256(code_verifier);
const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);

// Call My Account API to create a connected account
const res = await fetch("https://adam.local.dev.auth0.com/me/v1/connected-accounts/connect", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${at}`
},
body: JSON.stringify({
connection,
redirect_uri: "http://localhost:3000",
state: state,
code_challenge,
code_challenge_method: "S256",
authorization_params: {
scope: "openid profile email read:foo offline_access",
audience: "urn:api",
prompt: "consent"
}
})
});
const { connect_uri, connect_params, auth_session } = await res.json();
if (connect_uri) {
auth0Client.transactionManager.create({
state,
code_verifier,
auth_session
});

window.location.href = `${connect_uri}?ticket=${connect_params.ticket}`;
}
}

const connectAccountCallback = async () => {
const at = await auth0Client.getTokenSilently({ authorizationParams: { audience: 'https://adam.local.dev.auth0.com/me/', scope: 'create:me:connected_accounts' }});
// const result = await auth0Client.handleRedirectCallback();
const searchParams = new URLSearchParams(window.location.search);
const code = searchParams.get("connect_code");
const tx = auth0Client.transactionManager.get();

showContentFromUrl('/profile');
window.history.pushState({ url: '/profile' }, {}, '/profile');
// TODO validate state

const res = await fetch("https://adam.local.dev.auth0.com/me/v1/connected-accounts/complete", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${at}`
},
body: JSON.stringify({
"auth_session": tx.auth_session,
"connect_code": code,
"redirect_uri": "http://localhost:3000",
"code_verifier": tx.code_verifier,
})
});

auth0Client.transactionManager.remove();
if (res.ok) {
document.getElementById('connect-account').setAttribute('disabled', 'disaabled');
document.getElementById('connect-account').innerText = 'Connected!';
const data = await res.json();
document.getElementById("profile-data").innerText = JSON.stringify(data, null, 2);
}

};

/**
* Starts the authentication flow
*/
Expand Down Expand Up @@ -54,7 +158,10 @@ const configureClient = async () => {

auth0Client = await auth0.createAuth0Client({
domain: config.domain,
clientId: config.clientId
clientId: config.clientId,
useRefreshTokens: true,
useMrrt: true,
cacheLocation: "localstorage"
});
};

Expand Down Expand Up @@ -95,8 +202,19 @@ window.onload = async () => {
window.history.pushState({ url }, {}, url);
}
}
if (e.target.id === "connect-account") {
e.preventDefault();
connectAccount();
}
});

const query = window.location.search;
const hasConnectCode = query.includes("connect_code=") && query.includes("state=");

if (hasConnectCode) {
await connectAccountCallback();
}

const isAuthenticated = await auth0Client.isAuthenticated();

if (isAuthenticated) {
Expand All @@ -108,10 +226,9 @@ window.onload = async () => {

console.log("> User not authenticated");

const query = window.location.search;
const shouldParseResult = query.includes("code=") && query.includes("state=");

if (shouldParseResult) {
if (shouldParseResult && !hasConnectCode) {
console.log("> Parsing redirect");
try {
const result = await auth0Client.handleRedirectCallback();
Expand Down
Loading