10

I'm building a chrome extension and am trying to implement user login and sign-up. I originally had the sign-up and login functionality in the popup portion of my chrome extension but, after examining some of the more popular chrome extensions like Grammarly and Honey, I realized that they use their websites to login and sign up users. I decided to do the same for various reasons.

I'm using React js for both my website and the popup. I am using AWS-Amplify to handle login, sign-up, and user sessions. When I open the popup I have it check for a user session using await Auth.currentSession(); after having logged in on my site with await Auth.signIn(email, password);. However, that doesn't work. I've read the Amplify docs but couldn't find an answer. I have functionality in my popup that requires access to AWS services.

How can I use AWS-Amplify to login to my chrome extension via my website?

ggrant
  • 452
  • 7
  • 15

3 Answers3

13

I did end up figuring this out. I'm not sure if this is the "right way" to do this but it works. After logging in using amplify on my react web app I can grab the session and send it to the chrome extension. However, only JSONifible objects can be sent through the extension messaging api. So all the functions that come with the session are lost. However, you can rebuild the session from the information that can be sent through the messaging api. You rebuild the session, create a new CognitoUser object, and then attach the session to the user. Once that is done the session will be stored and amplify will be able to use it.

On the web side.

//Get the current session from aws amplify
const session = await Auth.currentSession();


const extensionId = 'your_extension_id';

chrome.runtime.sendMessage(extensionID, session,
        function(response) {
            // console.log(response);     
        });

On the extension side in background.js

// This is all needed to reconstruct the session
import {
    CognitoIdToken, 
    CognitoAccessToken, 
    CognitoRefreshToken, 
    CognitoUserSession,
    CognitoUser,
    CognitoUserPool
  } from "amazon-cognito-identity-js";
import {Auth} from "aws-amplify";

//Listen for incoming external messages.
chrome.runtime.onMessageExternal.addListener(
  async function (request, sender, sendResponse) {
    if (request.session) {
      authenticateUser(request.session);;
    } else {
      console.log(request);
    }
    sendResponse("OK")
  });

//Re-build the session and authenticate the user
export const authenticateUser = async (session) => {
    let idToken = new CognitoIdToken({
      IdToken: session.idToken.jwtToken
    });
    let accessToken = new CognitoAccessToken({
        AccessToken: session.accessToken.jwtToken
    });
    let refreshToken = new CognitoRefreshToken({
        RefreshToken: session.refreshToken.token
    });
    let clockDrift = session.clockDrift;
    const sessionData = {
      IdToken: idToken,
      AccessToken: accessToken,
      RefreshToken: refreshToken,
      ClockDrift: clockDrift
    }
    // Create the session
    let userSession  = new CognitoUserSession(sessionData);
    const userData = {
      Username: userSession.getIdToken().payload['cognito:username'],
      Pool: new CognitoUserPool({UserPoolId: YOUR_USER_POOL_ID, ClientId: YOUR_APP_CLIENT_ID})
    }
    
    // Make a new cognito user
    const cognitoUser = new CognitoUser(userData);
    // Attach the session to the user
    cognitoUser.setSignInUserSession(userSession);
    // Check to make sure it works
    cognitoUser.getSession(function(err, session) {
      if(session){
        //Do whatever you want here
      } else {
        console.error("Error", err);
      }
      
    })
    // The session is now stored and the amplify library can access it to do
    // whatever it needs to.
  }
ggrant
  • 452
  • 7
  • 15
  • To have chrome. runtime available on http (localhost) see https://stackoverflow.com/questions/53342993/why-is-chrome-runtime-undefined-when-opening-website-using-http-and-not-https, also need to add "externally_connectable": { "matches": [ "http://localhost/*"]} to have it work – nomadus Nov 03 '21 at 12:24
3

There is a configuration option in AWS Amplify where you can override the default token storage (which is localStorage in the browser)

class MyStorage {
    // the promise returned from sync function
    static syncPromise = null;
    // set item with the key
    static setItem(key: string, value: string): string;
    // get item with the key
    static getItem(key: string): string;
    // remove item with the key
    static removeItem(key: string): void;
    // clear out the storage
    static clear(): void;
    // If the storage operations are async(i.e AsyncStorage)
    // Then you need to sync those items into the memory in this method
    static sync(): Promise<void> {
        if (!MyStorage.syncPromise) {
            MyStorage.syncPromise = new Promise((res, rej) => {});
        }
        return MyStorage.syncPromise;
    }
}

// tell Auth to use your storage object
Auth.configure({
    // REQUIRED - Amazon Cognito Region
    region: process.env.REACT_APP_AWS_REGION,

    // OPTIONAL - Amazon Cognito User Pool ID
    userPoolId: process.env.REACT_APP_COGNITO_USER_POOL_ID,

    // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string)
    userPoolWebClientId: process.env.REACT_APP_COGNITO_USER_POOL_CLIENT_ID,

    // OPTIONAL - customized storage object
    storage: MyStorage
});

More information here

What you could do is something like this, using chrome.store API

export class TokensStorage {
  static setItem(key, value) {
    chrome.storage.local.set({[key]: JSON.stringify(value)}, () => {
      console.log('token stored');
    });
  }
  // get item with the key
  static getItem(key) {
    return new Promise((resolve, reject) => {
      chrome.storage.local.get([key], (result) =>  {
        resolve(result)
      });
    })
  }
  // remove item with the key
  static removeItem(key) {
    chrome.storage.local.remove(key, () => {
      console.log("item removed");
    })
  }
  // clear out the storage
  static clear() {
    chrome.storage.local.clear(() => {
      console.log("storage cleared");
    })
  }
}
Matteo
  • 2,256
  • 26
  • 42
0

Here is the solution I came up with while solving exactly the same problem. I hope this will also work for you: https://github.com/vocably/pontis

How to use it

npm install --save @vocably/pontis
// website-app/index.js

import { Auth } from '@aws-amplify/auth';
import { AppAuthStorage } from '@vocably/pontis';

const extensionId = 'baocigmmhhdemijfjnjdidbkfgpgogmb';

Auth.configure({
  // The following line sets up the custom storage
  // which exchages auth tokens with the extension
  storage: new AppAuthStorage(extensionId),
  // and the rest of Auth params:
  region: 'eu-central-1',
  userPoolId: 'eu-central-1_uSErPooL',
  //etc...
});
// extension/service-worker.js
import { registerExtensionStorage } from '@vocably/pontis';
import { Auth } from '@aws-amplify/auth';

// The only function param is responsible for
// the storage type. It could be 'sync' or 'local'
const storage = registerExtensionStorage('sync');

Auth.configure({
  // The following line sets up the custom extension
  // storage which exchanges Auth tokens with the app
  storage: storage,
  // and the rest of Auth params:
  region: 'eu-central-1',
  userPoolId: 'eu-central-1_uSErPooL',
  //etc...
});

That's it.

sneas
  • 924
  • 6
  • 23