Having trouble with implementing Spotify OAuth in NextJS?

I am in the process of creating a music website portfolio using Spotify and NextJS. I want to incorporate music playback on the website, but I am encountering issues with Spotify authentication. When I click the login button, I receive a 405 HTTP status error. I tried checking it using Postman and encountered the same error. After some research, I believe it might be related to GET/POST problems, but I am unsure about how to resolve it.

Below is the code snippet for the login part:

export async function LoginHandler (req: NextApiRequest, res: NextApiResponse) {
  const state = generateRandomString(16);
  const CLIENT_ID = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID;
  const scope =
    "user-read-private user-read-email user-read-playback-state user-modify-playback-state streaming";
  
  res.redirect(
    "https://accounts.spotify.com/authorize?"+
      qs.stringify({
        response_type: "code",
        client_id: CLIENT_ID,
        scope: scope,
        redirect_uri: `${REDIRECT_URL}/api/callback`,
        state: state,
      })
  );

I am utilizing useRouter from Next.js for routing:

<button
        onClick={() => {
          router.push("/api/login");
        }}
      >

Here is the code snippet to obtain the access token:

export const getAccessTokenData = () => {
  return axios<AccessTokenData>({
    method: "POST",
    url: SPOTIFY_ACCESS_TOKEN_URL,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization:
        "Basic " +
        Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"),
    },
    data: {
      grant_type: "client_credentials",
    },
  });
};

export const getAuthorizationTokenData = (code: string) => {
  return axios<AuthorizationTokenData>({
    method: 'post',
    url: 'https://accounts.spotify.com/api/token',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization:
        'Basic ' +
        Buffer.from(
          `${CLIENT_ID}:${CLIENT_SECRET}`
        ).toString('base64'),
    },
    data: {
      code: code,
      redirect_uri: REDIRECT_URL + '/api/callback',
      grant_type: 'authorization_code',
    },
  });
}

Answer №1

405 error indicates an invalid Authentication or incorrect format of the sent parameter. The data retrieved from getAuthorizationTokenData() was in an Incorrect Request Format.

If you need to send data in application/x-www-form-urlencoded format using an axios POST call, make sure to utilize the URLSearchParams class to structure the request body.

Familiarize yourself with the "Authorization Code Flow" for Spotify For more detailed information, refer to the guide here

https://i.sstatic.net/YFfis.png

The demo code showcases implementing Spotify login, accessing the access token through the "Authorization Code Flow", and retrieving specific playlists while displaying songs by using next.js.

File structure

I have included three files when creating a next.js project (index.tsx, callback.tsx, and constant.ts)

npx create-next-app@latest my-spotify-app --typescript

Please ensure to delete the file ./src/app/pages.tsx.

https://i.sstatic.net/IoLIH.png

Save the file index.tsx

// pages/index.tsx
import { GetServerSideProps } from 'next';
import { clientId, scope, redirectUri } from './constants';

const Home = ({ spotifyAuthUrl }: { spotifyAuthUrl: string }) => {
    return (
        <div>
            <h1>Spotify Login</h1>
            <a href={spotifyAuthUrl}>Login with Spotify</a>
        </div>
    );
};

export const getServerSideProps: GetServerSideProps = async () => {
    const encodeRedirectUri = encodeURIComponent(redirectUri);
    const spotifyAuthUrl = `https://accounts.spotify.com/authorize?response_type=code&client_id=${clientId}&redirect_uri=${encodeRedirectUri}&scope=${scope}`;
    return { props: { spotifyAuthUrl } };
};

export default Home;

Save the file callback.tsx

// pages/callback.tsx
import { GetServerSideProps } from 'next';
import axios from 'axios';
import { clientId, clientSecret, redirectUri, myPlaylistId } from './constants';

interface Track {
    track: {
        name: string;
        id: string;
    };
}

const Callback = ({ accessToken, playlistTracks }: { accessToken: string, playlistTracks: Track[] }) => {
    return (
        <div>
            <h1>Spotify Playlist Tracks</h1>
            <p>Access Token: {accessToken}</p>
            <ul>
                {playlistTracks.map((track, index) => (
                    <li key={index}>
                        {track.track.name}
                        <br />
                        <iframe
                            src={`https://open.spotify.com/embed/track/${track.track.id}`}
                            width="300"
                            height="80"
                            allow="encrypted-media"
                        ></iframe>
                    </li>
                ))}
            </ul>
        </div>
    );
};

export const getServerSideProps: GetServerSideProps = async ({ query }) => {
    const code = query.code as string;

    if (!code) {
        return {
            redirect: {
                destination: '/',
                permanent: false,
            },
        };
    }

    try {
        const response = await axios.post(
            'https://accounts.spotify.com/api/token',
            new URLSearchParams({
                grant_type: 'authorization_code',
                code,
                redirect_uri: redirectUri,
                client_id: clientId,
                client_secret: clientSecret,
            }).toString(),
            {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            }
        );

        const accessToken = response.data.access_token;

        const playlistResponse = await axios.get(
            `https://api.spotify.com/v1/playlists/${myPlaylistId}/tracks`,
            {
                headers: {
                    Authorization: `Bearer ${accessToken}`,
                },
            }
        );

        const playlistTracks: Track[] = playlistResponse.data.items;

        return { props: { accessToken, playlistTracks } };
    } catch (error) {
        console.error('Error:', error);
        return { props: { accessToken: '', playlistTracks: [] } };
    }
};

export default Callback;

Save the file constants.ts

// constants.ts
export const clientId = '[your client id]';
export const clientSecret = '[your client secret]';
export const redirectUri = 'http://localhost:3000/callback'; // replace your redirect URI
export const myPlaylistId = '[your playlist id]';
export const scope = 'playlist-read-private user-read-private user-read-email user-read-playback-state user-modify-playback-state streaming'; // Add more scopes if needed

Install dependency

npm insatall axios

Run it

npm run dev

Access it

http:/localhost:3000

Result

https://i.sstatic.net/J2NBo.png

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to handle a Node.js promise that times out if execution is not finished within a specified timeframe

return await new Promise(function (resolve, reject) { //some work goes here resolve(true) }); Using Delayed Timeout return await new Promise(function (resolve, reject) { //some work goes here setTimeout(function() { resolve(true); }, 5000); } ...

What is the best way to implement a user-customizable dynamic URL that incorporates API-generated content in a NextJS and React application?

Seeking assistance with implementing customizable dynamic URLs in Next.js with React. My current project involves a Next.js+React application that uses a custom server.js for routing and handling 'static' dynamic URLs. The goal now is to transiti ...

Activate the field once the input for the other field is completed

I have a form where the last name field is initially disabled. How can I make it so that the last name field becomes enabled only when the first name is inputted? <form> <label for="fname">First name:</label><br> ...

Transforming an Established React Project into a Progressive Web Application

Currently, I have an existing react tsx project that has been set up. My goal is to transform it into a PWA by adding service workers. However, after adding the service workers in the src folder, I encountered an error when attempting to deploy on firebase ...

What is the best way to assign an index signature to a plain object?

Currently, I have this object: const events = { i: 'insert', u: 'update', d: 'delete' }; I am struggling to assign an index signature to the object. When I try the following: export interface EventsSignature { [key: ...

Is there a way to implement a pipe function in TypeScript?

Introducing a unique pipe function implemented in plain JavaScript: const customPipe = (f, ...fs) => x => f === undefined ? x : customPipe(...fs)(f(x)) const exampleFunction = customPipe( x => x + 1, x => `wow ${x * 2} this is an amaz ...

Encountered a glitch when utilizing the callbacks feature in Next-auth

I am currently working on integrating the custom session from Next-Auth to my client-side application. The route.js file in my app/api/auth/[...nextauth] directory looks like this: import { MongoClient } from "mongodb"; import CredentialsProvider ...

What are the reasons for deprecating bindToController in Typescript?

When I am creating an AngularJS directive using TypeScript, I typically use the bindToController property to bind parameters to the controller for easy access. export class MyDirective implements IDirective { controller = MyController; controllerA ...

Is it possible to deduce the output type of a function based on its input?

In a web development project, the function getFormData() plays a crucial role in validating and sanitising a FormData object based on a specified schema. If the validation process goes smoothly without any errors, the function will return the cleansed Form ...

Uncertain about the distinction between reducers and dispatchers when it comes to handling actions

I'm feeling a bit confused regarding reducers and dispatchers. While both receive actions as parameters, it doesn't necessarily mean that the actions I use in my dispatchers are the same as those used in my reducers, correct? For example, if I h ...

Issues encountered while deploying Next.js on AWS Elastic Beanstalk

I encountered the following issue while deploying to AWS Elastic Beanstalk. Can anyone shed some light on why this error is happening? > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="36555a5f535842760618071806">[email& ...

Organize information by time intervals using JavaScript

I am currently facing an issue where I need to dynamically sort data from the server based on different fields. While sorting is working flawlessly for all fields, I am encountering a problem with the time slot field. The challenge lies in sorting the data ...

The call to the Angular service has no matching overload between the two services in Typescript with 13 types

I encountered an error while creating a new Angular project following a tutorial, and I'm seeking assistance to understand it. The error message reads: "No overload matches this call. Overload 1 of 5... Type 'Object' is missing the followi ...

Is there a way to access the final child element within a mat-accordion component using Material-UI and Angular 8?

I have a mat-accordion element with multiple expansion panels that are generated dynamically. How can I programmatically select and expand the last mat-expansion-panel element? <mat-accordion> <mat-expansion-panel> text 0 </mat-ex ...

Tips for automatically adjusting the row height in a table with a static header

On my page, I have a header, footer, and a single table with a fixed header. You can check out the code in the sandbox (make sure to open the results view in a new window). Click here for the code sandbox I am looking to extend the rows section so that i ...

Understanding the connection between two unions through TypeScript: expressing function return types

Within my codebase, I have two unions, A and B, each with a shared unique identifier referred to as key. The purpose of Union A is to serve as input for the function called foo, whereas Union B represents the result yielded by executing the function foo. ...

Error: Prettier is expecting a semi-colon in .css files, but encountering an unexpected token

I'm currently attempting to implement Prettier with eslint and TypeScript. Upon running npm run prettier -- --list-different, I encountered an error in all of my css files stating SyntaxError: Unexpected token, expected ";". It seems like there might ...

Angular 6: Exploring the Challenges of Extending Services Without Sacrificing the Functionality of ChildService

As I was developing multiple angular REST-services for my frontend, I came up with the idea of creating a base class BaseRestService to handle common functionalities like headers and helper functions. However, I encountered TypeErrors when trying to call ...

Arrange information in table format using Angular Material

I have successfully set up a component in Angular and Material. The data I need is accessible through the BitBucket status API: However, I am facing an issue with enabling column sorting for all 3 columns using default settings. Any help or guidance on th ...

Function not functioning as expected in NestJS MongoDB unique field feature

I am trying to set the "unique:true" attribute for the name property in my NestJS - MongoDB schema, but it is not working as expected by default. @Schema() export class User { @Prop() userId:string; @Prop({ type:String, required:true, } ...