How to Add Passwordless Auth to a Chrome Extension with Magic
#Quick Start
01git clone https://github.com/magiclabs/example-chrome-extension.git
02cd example-chrome-extension
03mv .env.example .env // enter your Magic Publishable API key
04yarn install
05yarn build
#Resources
View the example code here. Try out the browser extension here.
#Introduction
Maybe you want to allow users of your extension to have profiles, or maybe you want to charge for premium features. This tutorial shows how you can integrate Magic passwordless authentication into a Chrome browser extension using React.
After clicking the browser extension icon and then the "login" button, the user will be directed to a full-page tab to complete the auth flow. After that, clicking the browser extension will show the user authenticated!
Note: The new tab is required because if we had the login form inside the browser extension popup, as soon as the user navigated away to check their email for the link, the popup page would close and the auth flow would break.
#File Structure
Our application's file structure will look like this.
01├── .env
02├── build
03├── README.md
04├── package.json
05├── public
06│ ├── icon-purple.png
07│ ├── index.html
08│ └── manifest.json
09├── src
10│ ├── components
11│ │ ├── App.js
12│ │ ├── Loading.js
13│ │ ├── Login.js
14│ │ └── Profile.js
15│ ├── index.js
16│ ├── magic.js
17│ └── styles.css
18└── yarn.lock
The folder that we will be uploading to Chrome will be build
, which after you run yarn build
will look something like this.
01├── build
02│ ├── asset-manifest.json
03│ ├── icon-purple.png
04│ ├── index.html
05│ ├── manifest.json
06│ └── static
07│ ├── css
08│ │ ├── main.ee12e5fd.chunk.css
09│ │ └── main.ee12e5fd.chunk.css.map
10│ └── js
11│ ├── 2.c16f79f9.chunk.js
12│ ├── 2.c16f79f9.chunk.js.LICENSE.txt
13│ ├── 2.c16f79f9.chunk.js.map
14│ ├── main.bae666a1.chunk.js
15│ ├── main.bae666a1.chunk.js.map
16│ ├── runtime-main.33ea36a3.js
17│ └── runtime-main.33ea36a3.js.map
#Building the App
The Magic React app boilerplate will be taken from the Hello World (React)
template using the npx make-magic
command.
01$ npx make-magic
02npx: installed 1 in 1.472s
03
04
05 █▀▀ █▀█ █▀▀ ▄▀█ ▀█▀ █▀▀
06 █▄▄ █▀▄ ██▄ █▀█ █ ██▄
07
08 █▀▄▀█ ▄▀█ █▀▀ █ █▀▀
09 █ ▀ █ █▀█ █▄█ █ █▄▄
10
11 ▄▀█ █▀█ █▀█
12 █▀█ █▀▀ █▀▀
13
14
15Running scaffold create-magic-app
16
17✔ What is your project named? · example-chrome-extension
18✔ Choose a template: · hello-world-react
19✔ Enter your Magic publishable API key: · pk_live_...
20✔ Choose an NPM client: yarn
Since there's no redirect back to the chrome extension, go ahead and delete
- the
redirectURI
parameter given tologinWithMagicLink()
- the
/components/Callback.js
component - the
/callback
route inApp.js
#Manifest.json
Every browser extension needs a manifest.json
file, which tells Chrome important information about your extension, such as your icons, permissions (tab controls, storage access, etc), which scripts should be loaded into users browser windows, and much more.
01{
02 "name": "Magic Chrome Extension",
03 "description": "Build a Chrome extension with Magic auth!",
04 "version": "0.1.0",
05 "manifest_version": 3,
06 "action": {
07 "default_popup": "index.html",
08 "default_icon": {
09 "16": "/icon-purple.png",
10 "48": "/icon-purple.png",
11 "128": "/icon-purple.png"
12 }
13 },
14 "icons": {
15 "16": "/icon-purple.png",
16 "48": "/icon-purple.png",
17 "128": "/icon-purple.png"
18 }
19}
#Login.js
A few changes need to be made to the Login.js
component which was generated for us with the npx make-magic
command. If the window is > 400 pixels wide, it's safe to assume the user is on the full page view, and if so, we'll display the full login form. Otherwise we are assuming the user is viewing in the actual extension popup, so we'll display a button to login, which will create a new full page tab with the login form.
01export default function Login() {
02 const [email, setEmail] = useState('');
03 const [isLoggingIn, setIsLoggingIn] = useState(false);
04 const [isFullPage, setIsFullPage] = useState(false);
05 const history = useHistory();
06
07 /* Relying on the page width to tell if the user is viewing in the popup or full page */
08 useEffect(() => {
09 if (window.innerWidth > 400) setIsFullPage(true);
10 }, []);
11
12 /**
13 * Perform login action via Magic's passwordless flow. Upon successuful
14 * completion of the login flow, a user is redirected to the homepage.
15 */
16 const login = useCallback(async () => {
17 setIsLoggingIn(true);
18
19 try {
20 await magic.auth.loginWithMagicLink({ email });
21 history.push('/');
22 } catch {
23 setIsLoggingIn(false);
24 }
25 }, [email]);
26
27 /**
28 * Saves the value of our email input into component state.
29 */
30 const handleInputOnChange = useCallback(event => {
31 setEmail(event.target.value);
32 }, []);
33
34 return (
35 <div className="container">
36 {!isFullPage ? (
37 <button onClick={() => chrome.tabs.create({ url: 'index.html' })}>Login</button>
38 ) : (
39 <>
40 <h1>Please sign up or login</h1>
41 <input
42 type="email"
43 name="email"
44 required="required"
45 placeholder="Enter your email"
46 onChange={handleInputOnChange}
47 disabled={isLoggingIn}
48 />
49 <button onClick={login} disabled={isLoggingIn}>
50 Send
51 </button>
52 </>
53 )}
54 </div>
55 );
56}
#Running the App Locally
You need to upload your build
folder to Chrome to test your extension locally. Run the following command in your terminal to create the build
folder.
01yarn build
Next, visit chrome://extensions
in your Chrome URL bar. Then toggle Developer Mode
on in the top right.
#Potential Errors
You may run into an error such as
01Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"...
Chrome has CSP settings to block certain scripts from being run. If you notice yourself running into this, make sure to include INLINE_RUNTIME_CHUNK=false
in your .env
file when running yarn build
.
#Done
Your browser extension app is now secured with Magic! You can follow this link for instructions on how to publish your new extension to the Chrome Web Store.