javascriptnode.jsspotifykoa

Koa.js Redirect Not Working as Expected - Returns "Not Found"


I'm new to Koa.js, and I'm trying to create a simple authentication flow with Spotify. Here's my code:

import koa from "koa";
import querystring from "node:querystring";

const port = 54832;
const client_id = "myClientID";
const redirect_uri = `http://localhost:${port}/callback`;
const scope = "user-read-currently-playing";

export default function requestAuth() {
    const server = new koa();
    let response = {};

    server.use(async (ctx) => {
        const { url, method } = ctx.request;

        if (method === "GET" && url === "/login") {
            ctx.body = "You will be redirected to Spotify auth page";

            ctx.redirect(
                "https://accounts.spotify.com/authorize?" +
                    querystring.stringify({
                        response_type: "code",
                        client_id: client_id,
                        scope: scope,
                        redirect_uri: redirect_uri,
                    })
            );
        } else if (method === "GET" && url.includes("/callback")) {
            if (!ctx.request.query.error) {
                response.code = ctx.request.query.code;
            } else {
                console.log(ctx.request.query.error);
            }
        }
    });

    server.listen(port);

    return response;
}

I want to redirect users to the Spotify login page when they visit the /login URL. However, instead of redirecting to Spotify, I am being redirected to the local URL http://localhost:54832/response_type=code&client_id=myClientID&redirect_uri=http%3A%2F%2Flocalhost%3A54832&scope=user-read-currently-playing and getting a "Not Found" output in the response body.

Am I doing something wrong with the way I'm handling the redirect? Could it be an issue with my server setup or with the way Koa.js handles redirects? Any suggestions on how to troubleshoot or fix this?

A little earlier I had the same problem with Express JS. As I understood then, the problem was caching, which is difficult to disable in this framework. This is exactly why I switched to Koa JS. But since the problem persists in this case too, I am completely confused


Solution

  • The function requestAuth() returns an object immediately, before any HTTP requests are processed, so it's always empty and not useful.

    Here's how you might refactor the code to make it work more effectively, adhering to the typical use of Koa for handling multiple asynchronous requests

    Save as demo.js

    import Koa from 'koa'
    import Router from 'koa-router'
    import bodyParser from 'koa-bodyparser'
    import cors from '@koa/cors'
    import axios from 'axios'
    import querystring from "querystring";
    
    const app = new Koa()
    const router = new Router()
    
    const CLIENT_ID = "{your client id}";
    const CLIENT_SECRET = "{your client secret}";
    const PORT = 3000; // replace your port
    const REDIRECT_URI = `http://localhost:${PORT}/callback`
    const SCOPE = 'user-read-currently-playing'
    
    app.use(cors());
    app.use(bodyParser());
    
    const getToken = async (code) => {
        try {
            const response = await axios.post('https://accounts.spotify.com/api/token',
                querystring.stringify({
                    'grant_type': 'authorization_code',
                    'code': code,
                    'redirect_uri': REDIRECT_URI
                }),
                {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    auth: {
                        username: CLIENT_ID,
                        password: CLIENT_SECRET
                    }
                });
            return response.data.access_token
        } catch (err) {
            console.error(err)
            throw err
        }
    }
    
    router.get('/login', async (ctx) => {
        const redirect_url = `https://accounts.spotify.com/authorize?${
            querystring.stringify({
                response_type: 'code',
                client_id: CLIENT_ID,
                scope: SCOPE,
                state: '123456',
                redirect_uri: REDIRECT_URI,
                prompt: 'consent'
            })}`
        await ctx.redirect(redirect_url)
    })
    
    router.get('/callback', async (ctx) => {
        const code = ctx.query.code;
        try {
            const access_token = await getToken(code);
            ctx.body = { 'access_token': access_token };
        } catch (error) {
            console.error(error.message)
            ctx.status = 500
            ctx.body = { error: 'Failed to retrieve access token' }
        }
    })
    
    app.use(router.routes()).use(router.allowedMethods())
    
    app.listen(PORT, () => {
        console.log(`Listening on http://localhost:${PORT}`)
    })
    

    Install dependencies

    npm install koa koa-router koa-bodyparser @koa/cors axios querystring
    

    package.json

    {
      "type": "module",
      "dependencies": {
        "@koa/cors": "^5.0.0",
        "axios": "^1.6.8",
        "cors": "^2.8.5",
        "koa": "^2.15.3",
        "koa-bodyparser": "^4.4.1",
        "koa-router": "^12.0.1",
        "querystring": "^0.2.1"
      }
    }
    

    Run it

    node demo.js
    

    Login by Browser

    http://localhost:3000/login
    

    Replace 3000 into you port number

    Result

    enter image description here