Giter Club home page Giter Club logo

spa-pkce's Introduction

This is a markdown file showing the code snippets to include in the blogpost for the PKCE grant in the browser.

Start the login with a redirection

This is the logic needed for triggering the login process:

document.getElementById('login').addEventListener('click', (e) => {
  e.preventDefault();

  const config = await getConfig();
  const state = randomString(32);
  const codeVerifier = randomString(32);
  const codeChallenge = await sha256(codeVerifier).then(bufferToBase64UrlEncoded);

  // we need to store the state to validate the callback
  // and also the code verifier to send later
  sessionStorage.setItem(`login-code-verifier-${state}`, codeVerifier);

  const authorizationEndpointUrl = new URL(config.authorization_endpoint);

  // here we encode the authorization request
  authorizationEndpointUrl.search = new URLSearchParams({
    audience: API_AUDIENCE,
    redirect_uri: REDIRECT_URI,
    client_id: AUTH0_CLIENT_ID,
    response_type: 'code',
    scope: 'openid profile email read:appointments',
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state: state
  });

  window.location.assign(authorizationEndpointUrl);
});

Handle the callback of a redirection

To handle the callback we need to do this:

async function handleCallback() {
  const search = new URLSearchParams(window.location.search);
  if(!search.has('code')) { return; }
  const code = search.get('code');
  const state = search.get('state');
  const code_verifier = sessionStorage.getItem(`login-code-verifier-${state}`);

  if (!code_verifier) {
    console.error('unexpected state parameter');
    return;
  }

  sessionStorage.has
  const config = await getConfig();

  // exchange the authorization code for a tokenset
  const tokenSet = await fetch(config.token_endpoint, {
    method: 'POST',
    body: new URLSearchParams({
      audience: API_AUDIENCE,
      client_id: AUTH0_CLIENT_ID,
      redirect_uri: REDIRECT_URI,
      grant_type: 'authorization_code',
      code_verifier,
      code,
    }),
    headers: new Headers({
      'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
    })
  }).then(r => r.json());

  //this has access_token, id_token, expires_in, scope, token_type.
  console.dir(tokenSet);

  window.tokenSet = tokenSet;
  window.verifier = code_verifier

  //remove the querystring from the url in the address bar
  const url = new URL(window.location);
  url.search = '';
  window.history.pushState('', document.title, url);
}

Get a token silently

To get a token silently:

refreshBtn.addEventListener('click', async function(event) {
  event.preventDefault();
  const config = await getConfig();

  const state = randomString(32);
  const codeVerifier = randomString(32);
  const codeChallenge = await sha256(codeVerifier).then(bufferToBase64UrlEncoded);
  const authorizationEndpointUrl = new URL(config.authorization_endpoint);

  // here we encode the authorization request
  authorizationEndpointUrl.search = new URLSearchParams({
    audience: API_AUDIENCE,
    redirect_uri: REDIRECT_URI,
    client_id: AUTH0_CLIENT_ID,
    response_type: 'code',
    response_mode: 'web_message',
    prompt: 'none',
    scope: 'openid profile email read:appointments',
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state: state
  });

  //load the url in an iframe and wait for the response
  const authorizeResponse = await new Promise((resolve, reject) => {
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none';

    const timeoutSetTimeoutId = setTimeout(() => {
      reject(new Error('timed out'));
      window.document.body.removeChild(iframe);
    }, 60 * 1000);

    function responseHandler(e) {
      if (e.origin !== authorizationEndpointUrl.origin ||
          e.data.type !== 'authorization_response') {
        return;
      }
      e.source.close();
      clearTimeout(timeoutSetTimeoutId);
      window.removeEventListener('message', responseHandler, false);
      window.document.body.removeChild(iframe);
      const response = e.data.response;
      if(response.error) {
        return reject(response)
      }
      if (response.state !== state) {
        return reject(new Error("State does not match."));
      }
      resolve(response);
    };

    window.addEventListener('message', responseHandler);
    window.document.body.appendChild(iframe);
    iframe.setAttribute('src', authorizationEndpointUrl);
  });

    // exchange the authorization code for a tokenset
  const tokenSet = await fetch(config.token_endpoint, {
    method: 'POST',
    body: new URLSearchParams({
      audience: API_AUDIENCE,
      client_id: AUTH0_CLIENT_ID,
      redirect_uri: REDIRECT_URI,
      grant_type: 'authorization_code',
      code_verifier: codeVerifier,
      code: authorizeResponse.code,
    }),
    headers: new Headers({
      'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
    })
  }).then(r => r.json());

  console.dir(tokenSet);
});

This example uses some primitives found in moderm browsers and some utility methods availables in util.js.

spa-pkce's People

Contributors

jfromaniello avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

spa-pkce's Issues

Storing the token set

Hey, first of all: Great example! Really helped me to understand the pkce flow in practice.

Now my question:
Is there any specific reason that the token set is stored under window.tokenSet?

As far as I know that should be vulnerable to XSS attacks too. So it basically does not matter if I store the token under window.tokenSet, in localStorage, sessionStorage or a cookie, right?

License

Can you add a license to this project ? For instance Apache License 2.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.