top of page
  • Writer's pictureAn Object Is A

Discord OAuth2 Login for Chrome Extensions

Updated: Nov 9, 2021



 

This tutorial is based on Chrome Extension Manifest Version 2.

Let's Begin.


Before we even touch a line of code, we need to setup our development work space so that we have the ability to use Discord's OAuth2 Endpoint.



Click 'New Application' in the top right.

Name it whatever you want.

Keep this window open, we'll need that 'CLIENT ID' a little later.


Navigate to chrome://extensions and make sure your Chrome Extension is loaded.

Copy the 'ID' of your extension and head back to the Discord Developer Portal.


Click the 'OAuth2' link on the left sidebar.

Click on 'Add Redirect' and add the URL, https://.chromiumapp.org/ where is the extension id you copied earlier.


Maker sure to click Save Changes.




We can now use Discord OAuth2 Login for Chrome Extensions.


 

Let's do some web development work before we get to actual Chrome Extension work.


We'll create two pages: A 'Sign In' page and a 'Sign Out' page. Nothing fancy.

/* popup-sign-in.html */
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            body {
                width: 300px;
                height: 600px;
                margin: 0;
                padding: 0;
                overflow: hidden;
            }

            div {
                align-items: center;
                display: flex;
                width: 100%;
                height: 100%;
                justify-content: center;
                text-align: center;
                margin: auto;
                box-sizing: border-box;
                background-color: #fcee54;
            }

            button {
                font-size: 200%;
                background-color: #f5c2e0;
                border-radius: 5px;
                border: none;
                text-align: center;
                color: black;
                font-family: monospace;
                font-weight: bold;
                transition-duration: 0.3s;
                padding: 10px;
            }
        </style>
    </head>
    <body>
        <div>
            <button type="submit">Sign In</button>
        </div>
        <script src="./popup-sign-in-script.js"></script>
    </body>
</html>
/* popup-sign-out.html */
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            body {
                width: 300px;
                height: 600px;
                margin: 0;
                padding: 0;
                overflow: hidden;
            }

            div {
                align-items: center;
                display: flex;
                width: 100%;
                height: 100%;
                justify-content: center;
                text-align: center;
                background-color: #00ffa7;
                transition-duration: 0.5s;
            }

            button {
                font-size: 200%;
                background-color: #f5c2e0;
                border-radius: 5px;
                border: none;
                text-align: center;
                color: black;
                font-family: monospace;
                font-weight: bold;
                transition-duration: 0.3s;
                padding: 10px;
            }
        </style>
    </head>
    <body>
        <div>
            <button type="submit">Sign Out</button>
        </div>
        <script src="./popup-sign-out-script.js"></script>
    </body>
</html>

Note: Make sure you include the body CSS properties.

The other CSS you don't really need. It's just used to make the page look good.


Notice that we've attached scripts to each of our HTML pages…

/* popup-sign-in-script.js */
const button = document.querySelector('button');

button.addEventListener('mouseover', () => {
    button.style.backgroundColor = 'black';
    button.style.color = 'white';
    button.style.transform = 'scale(1.3)';
});

button.addEventListener('mouseleave', () => {
    button.style.backgroundColor = '#f5c2e0';
    button.style.color = 'black';
    button.style.transform = 'scale(1)';
});

button.addEventListener('click', () => {
});
/* popup-sign-out-script.js */
const button = document.querySelector('button');

button.addEventListener('mouseover', () => {
    button.style.backgroundColor = 'black';
    button.style.color = 'white';
    button.style.transform = 'scale(1.3)';

    document.querySelector('div').style.backgroundColor = '#ee2f64';
});

button.addEventListener('mouseleave', () => {
    button.style.backgroundColor = '#f5c2e0';
    button.style.color = 'black';
    button.style.transform = 'scale(1)';

    document.querySelector('div').style.backgroundColor = '#fcee54';
});

button.addEventListener('click', () => {
});

Note:

A lot of this code is completely unnecessary. It's just used to make the page look nice and animate.

The only code that matters in the scripts are the 'click' listeners for the buttons.


Now that we have the Web Dev portion out of the way, let's take a look at our 'manifest.json'.

{
    "name": "obj ext",
    "description": "my ext",
    "version": "0.1.0",
    "manifest_version": 2,
    "icons": {
        "16": "./obj-16x16.png",
        "32": "./obj-32x32.png",
        "48": "./obj-48x48.png",
        "128": "./obj-128x128.png"
    },
    "background": {
        "scripts": ["./background.js"]
    },
    "options_page": "./options.html",
    "browser_action": {
        "default_popup": "./popup-sign-in.html"
    },
    "permissions": [
        "identity"
    ] 
}

Note:

  1. The "default_popup" property of the "browser_action" is set to the "Sign In" page.

  2. We need the 'identity' permission in order to use Chrome’s 'launchWebAuthFlow()' method.


 

Let's do some actual Chrome Extension programming.


We'll start by coding the basic skeletal logic flow of our app.


In the 'popup-sign-in-script.js', when the user clicks on the button, we'll send a message to the 'background' script asking to "login".

If we get a "success" from the 'background' we'll change the page to the "Sign Out" page.

// popup-sign-in-script.js

const button = document.querySelector('button');

button.addEventListener('mouseover', () => {
    button.style.backgroundColor = 'black';
    button.style.color = 'white';
    button.style.transform = 'scale(1.3)';
});

button.addEventListener('mouseleave', () => {
    button.style.backgroundColor = '#f5c2e0';
    button.style.color = 'black';
    button.style.transform = 'scale(1)';
});

button.addEventListener('click', () => {
    chrome.runtime.sendMessage({ message: 'login' }, function (response) {
        if (response === 'success') window.location.replace("./popup-sign-out.html");
    });
});

The 'popup-sign-out-script.js' is almost identical.


In the 'popup-sign-out-script.js', when the user clicks on the button, we'll send a message to the 'background' script asking to "logout".

If we get a "success" from the 'background' we'll change the page to the "Sign In" page.

// popup-sign-out-script.js

const button = document.querySelector('button');

button.addEventListener('mouseover', () => {
    button.style.backgroundColor = 'black';
    button.style.color = 'white';
    button.style.transform = 'scale(1.3)';

    document.querySelector('div').style.backgroundColor = '#ee2f64';
});

button.addEventListener('mouseleave', () => {
    button.style.backgroundColor = '#f5c2e0';
    button.style.color = 'black';
    button.style.transform = 'scale(1)';

    document.querySelector('div').style.backgroundColor = '#fcee54';
});

button.addEventListener('click', () => {
    chrome.runtime.sendMessage({ message: 'logout' }, function (response) {
        if (response === 'success') window.location.replace("./popup-sign-in.html");
    });
});

This file is done. You can close it.


Moving to the 'background.js' script, we'll create the Discord OAuth2 Endpoint that's we'll use for 3rd party login credentials.

We're going to need 6 CONSTANTS and 1 VARIABLE.

And while we're at it, a variable to keep track of the user's login status and we'll create a function to bring all of this information together.

// background.js

const DISCORD_URI_ENDPOINT = 'https://discord.com/api/oauth2/authorize';
const CLIENT_ID = encodeURIComponent('');
const RESPONSE_TYPE = encodeURIComponent('token');
const REDIRECT_URI = encodeURIComponent('https://pcojhoejgkedcoikfdehlpfefhagppnf.chromiumapp.org/');
const SCOPE = encodeURIComponent('identify email');
const STATE = encodeURIComponent('meet' + Math.random().toString(36).substring(2, 15));

let user_signed_in = false;

function create_auth_endpoint() {
    let nonce = encodeURIComponent(Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15));

let endpoint_url =
    `${DISCORD_URI_ENDPOINT}
?client_id=${CLIENT_ID}
&redirect_uri=${REDIRECT_URI}
&response_type=${RESPONSE_TYPE}
&scope=${SCOPE}
&nonce=${nonce}`;

    return endpoint_url;
}

Note:

  1. DISCORD_URI_ENDPOINT — how we get to Discord's OAuth2 Endpoint

  2. CLIENT_ID — tells Discord we're allowed to use their OAuth2 Endpoint

  3. RESPONSE_TYPE — asks Discord for a specific category of information

  4. REDIRECT_URI — where to redirect the user after giving us the token

  5. SCOPE — asks Discord for specific data

  6. STATE — helps personalize our request

We have that last variable, 'nonce', created in the function.

'nonce' is simply a string that gets randomly generated every time we need to use the Discord OAuth2 Endpoint.

It needs to be different every time. This is why it's not a CONSTANT.


 

Let's bring this all together.


When our 'background.js' script gets the message to "login", we'll call the 'chrome.identity.launchWebAuthFlow()' function.


It takes two arguments.
  1. an object with our constructed OAuth2 endpoint and the 'interactive' flag of true (this allows the user to see the Discord prompt for credentials).

  2. a callback function that gives us a 'redirect uri' from Discord’s servers. We can use a "token" delivered to us to gain access to the user-who-logged-in's Discord data. We won’t be doing that in this video; we're simply using this endpoint to "Authenticate" the user, not "Authorize" us.

// background.js

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.message === 'login') {
        chrome.identity.launchWebAuthFlow({
            url: create_auth_endpoint(),
            interactive: true
        }, function (redirect_uri) {
            if (chrome.runtime.lastError || redirect_uri.includes('access_denied')) {
                console.log("Could not authenticate.");
                sendResponse('fail');
            } else {
                user_signed_in = true;
                sendResponse('success');
            }
        });
        return true;
    } 
//...

Note: In addition to checking for a chrome.runtime error, we also check that the user signed in successfully. It they don't, an "error=access_denied" string will be found in the 'redirect_uri'.


The "logout" branch is really simple.


Just flip the 'user_signed_in' flag to false and send a response of "success".

// background.js

//...
    } else if (request.message === 'logout') {
        user_signed_in = false;
        sendResponse('success');
    }
});

 

We're done.


When the user hits the "Sign In" button, they'll be greeted with Discord's Login System. If they successfully sign in, they'll be shown our "Sign Out" page.




You can find the source files here.


Discord Login System with Chrome Extensions | OAuth2/OpenID Connect


653 views0 comments

Recent Posts

See All
bottom of page