简体   繁体   中英

OAuth2 Discord authorization popup flow

I'm working on an admin panel for my discord bot and I'm currently implementing OAuth2 authorization.

My frontend and backend are hosted on two separate servers, and I don't exactly know how to handle sending/storing the access token. My current workflow relies on redirecting the discord authorization to the backend (I don't want it to redirect to frontend, because it would involve additional calls to the backend with the client code to create the token anyway), and I'm able to call the discord API with received client code and get the access token, but I have no idea how to store it properly.

Current flow:

  1. In the browser (localhost:3000) I click the Login button that opens the popup with discord authorization site.
const LINK = "https://discord.com/api/oauth2/authorize?client_id=somecode&redirect_uri=http%3A%2F%2F127.0.0.1%3A3010%2Fapi%2Fauth&response_type=code&scope=identify%20guilds";

function onLinkClicked(event: Event) {
    event.preventDefault();

    const popup = window.open(LINK, 'popup', 'width=600,height=800');
}
  1. After authorization I redirect to the server url (localhost:3010/api/auth?code=somecode).
  2. In there I'm making a call to the discord API to receive access token.
MainRouter.get('/auth', async (req, res) => {
    const code = req.query.code;
    const params: any = {
        'client_id': process.env.APP_ID,
        'client_secret': process.env.APP_SECRET,
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': 'http://127.0.0.1:3010/api/auth'
    };

    try {
        const tokenResponse = await axios.post(`${API_ENDPOINT}/oauth2/token`, new URLSearchParams(params));
        req.session.token = tokenResponse.data.access_token; // when I make another call from the frontend, it doesn't see the token stored in the session here, because I'm saving it in the wrong place, sessionID here doesn't match my sessionID when I make calls from the frontend itself.
        res.send('OK');
    } catch(error) {
        console.error(error);
    }
});

And now I'm stuck because I can't store the token in the session/cookies and I also don't know how could I store it in the database.

I don't want the discord authorization to redirect to the frontend site, where I will make another call to the backend with the client code and then convert it into access token and store it, because it seems messy and I hope there could be another solution.

I saw that probot.io site, when it creates authorization popup, it then redirects you to the api.probot.io?code=somecode, which is a different site, closes immediately and logs you into the dashboard and I don't know how to achieve similar effect.

So, my question is, is there a way to store the access token in the frontend session, when I'm not calling from the frontend? And since the answer is probably no, how can improve this workflow?

I came up with solution that seems okay, but I'm opened to suggestions.

Instead of instantly getting access token after the redirect to the /api/auth on backend, I just send the message from the popup to the main client containing the client code.

MainRouter.get('/auth', async (req, res) => {
    const code = req.query.code;
    
    res.set('Content-Type', 'text/html');
    res.send(Buffer.from(`<script>window.opener.postMessage("${code}", "http://localhost:3000");window.close();</script>`));
});

After receiving the code on client I make another /api/auth call, but this time it is POST and it expects the code as a parameter.

window.addEventListener('message', async (event) => {
    if(event.origin == 'http://127.0.0.1:3010') {
        console.log(event.data);
        const res = await callAPI('/auth', { code: event.data }, 'post');
        console.log(res);
    }
});

The backend then will exchange it for the access token and store it in the session, which this time works perfectly.

MainRouter.post('/auth', async (req, res) => {
    const code = req.body.code;
    const params: any = {
        'client_id': process.env.APP_ID,
        'client_secret': process.env.APP_SECRET,
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': 'http://127.0.0.1:3010/api/auth'
    };

    try {
        const tokenResponse = await axios.post(`${API_ENDPOINT}/oauth2/token`, new URLSearchParams(params));
        console.log(tokenResponse.data.access_token);

        req.session.token = tokenResponse.data.access_token; // this will work now, since I'm calling from the frontend side
        res.send({ code: 200 });
    } catch(error) {
        res.send({ code: 301, msg: error });
    }
});

I came up with this after realizing that I can send the messages from the popup to the main, but I don't know if it's the most optimal and somewhat secure solution. I'm thinking if there exists a "cleaner" solution to the problem though...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM