Keyri QR Login with Auth0

Incorporating Keyri QR login into your Auth0-enabled app is a straightforward process entailing sending your user's valid refreshToken from your mobile app, through the Keyri mobile SDK, to the Keyri QR web widget embedded on your web app. Scripts, detailed below, on your web app, exchange the refreshToken for their id_token and access_token. Those can be set into your web app's LocalStorage or as cookies in order to establish the user's login state on your web app.

Web

Full source code for a simple Next.js-based application utilizing Keyri QR login with Auth0 is available here: https://github.com/Keyri-Co/keyri-auth0-web (opens in a new tab)

The live demo is visible here: https://keyri-auth0-web.vercel.app/ (opens in a new tab)

The following video outlines the steps required to complete integration of the Keyri QR widget on your Auth0-enabled web app as well as the required Auth0-related API calls and other functions required to finalize authenticating the user.


QR Widget Embedding

First, embed the Keyri QR widget in a div of your choosing. Remember to save the KeyriQR.html file in a publicly-accessible directory on your server and set its location as the src of the Keyri iframe.

<div>
    <p className="lead" data-testid="hero-lead">
      Log in by scanning the QR code below with the example Keyri-Auth0 app.
     </p>
     <iframe
       src="./KeyriQR.html"
       id="qr - frame"
       frameBorder={0}
       height={300}
       width={300}
       scrolling="no"
       style={{ borderWidth: 0 }}></iframe>
 </div>

Event Listener Targeting QR Widget

Now set up an listener for events emitted by the Keyri QR widget - it will emit a payload after a user has initiated and accepted the QR login. The following is a React-based example that uses the useEffect hook

useEffect(() => {
  window.addEventListener('message', async (evt) => {
    if (
      evt.data.keyri &&
      evt.data.data &&
      document.location.origin == evt.origin
    ) {
      const { data } = evt;
      if (!data.error) {
        let refresh_token = JSON.parse(data.data).refreshToken;
        await handleQrLogin(refresh_token);
      } else if (data.error) {
        console.log(`Keyri error: ${data.message}`);
      }
    }
  });
}, []);

QR Login Handling Function

Finally, set up a function, referenced in the event listener written above, for handling the payload emitted by the Keyri widget. This is the part that exchanges the refreshToken for a usable id_token and access_token.

const handleQrLogin = async (refresh_token) => {
  try {
    // Exchange the refresh token for an access token and id token
    const res = await fetch('https://keyri.us.auth0.com/oauth/token', {
      method: 'POST',
      headers: {
        'content-type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        // The Auth0 client_id and client_secret below must match those of your MOBILE application
        client_id: 'K4RWxNFhsHU7xDTxVqGzW9x2JWlf5ily',
        client_secret:
          '7RlMb6UVQrThv6bhnhi-ZlYPKH5to8UPstuMmCrefBsSCgnMwe2O_iDUxLX5gEIg',
        refresh_token: refresh_token,
      }),
    });
    const data = await res.json();
 
    // Localstorage is not accessed in this example, but you can use it to store the tokens that the Auth0 API returns
    localStorage.setItem('access_token', data.access_token);
    localStorage.setItem('id_token', data.id_token);
 
    // Decode the JWT to get the user's id
    const jwt = data.id_token.split('.')[1];
    const decodedJwt = JSON.parse(atob(jwt));
 
    // Set the logged in state
    setUserId(decodedJwt.sub);
    setAuthenticated(true);
  } catch (error) {
    console.log(error);
  }
};

Mobile

Source code for an example iOS app that works with the Auth0 web project outlined above is available here: https://github.com/Keyri-Co/auth0-iOS (opens in a new tab)

The following video details how to set up your Auth0 project and how to send the user's refreshToken as the payload when initiating Keyri QR login.


Some specific configurations are required on Auth0 to make Keyri QR login work without compromising security.

Create Mobile Application on Auth0

Create Application

On Auth0's console, create a new application and select Native.

Configure URIs for Mobile App

In the settings page, configure the logout and callback URL using the format provided in the prompt. For iOS (shown above) you simply need to use your bundle id with the Auth0 app id, and with Android, the pagage name is used in the same format.

Install Keyri and Auth0 SDKs

Then, using your preferred package manager for your application, install the Keyri and Auth0 SDKs. For iOS, we support Cocoapods and SPM, for Android, we support Gradle. We also support React Native and Flutter.

Code

The coding portion is quite easy. The steps are

  1. Authenticate with Auth0 (note - it is important to use the "offline_access" scope, without it no refresh token will be provided)

  2. Extract the refresh token from the returned credential

  3. Utilize Keyri's built in API to send it to our service (this can be done with one line of code, as displayed below)

    1. The App Key can be obtained from the Keyri developer portal

    2. The token needs to be sent as a json string, i.e., {"refreshToken": "\[token"}

Auth0
    .webAuth()
    .scope("offline_access")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
            // 2
            print(credentials.refreshToken)
 
            let keyri = KeyriInterface(appKey: appKey, publicApiKey: publicApiKey, serviceEncryptionKey: serviceEncryptionKey)
            let payload = "{\"refreshToken\":\"\(credentials.refreshToken ?? "")\"}"
            print(payload)
 
            // 3
            keyri.easyKeyriAuth(payload: payload, publicUserId: username) { easyKeyriAuthRes in
                switch easyKeyriAuthRes {
                    case .success:
                        print("Successfully authenticated")
                    case .failure(let error):
                        print("Failed with: \(error)")
                }
            }
    }
 
}