Search code examples
node.jstypescriptreact-nativebackendspotify

Spotify Auth access token givin error code 400


I have been trying to get my spotify access tokens and refresh tokens for my react native application and its been giving me serious issues. https://docs.expo.dev/guides/authentication/#spotify I was able to get my code and state using the useAuthRequest. Now i have tried using the code to get my access tokens and refresh tokens for my application on the client but i keep encountering the error 400. So checking other stack overflow issues i realized handling it on the server would be better so i decided to create an express server to try and get the access code

router.post('/get-spotify-access-code',(req: Request, res: Response)=>{
  console.log(req.body)
  const accessCode = req.body;
  var authOptions:AuthOptions = {
    url: 'https://accounts.spotify.com/api/token',
    form: {
      code: accessCode,
      redirect_uri: redirectUri,
      grant_type: 'authorization_code'
    },
    headers: {
      'content-type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic ' + Buffer.from(clientID + ':' + clientSecret).toString('base64')
    },
    json: true
  };
  request.post(authOptions, (error: any, response:any, body:any)=>{
    console.log(error);
    console.log(response)
    if(!error && response.statusCode === 200){
      const access_token = body.access_token;
      const refresh_token = body.refresh_token;
      const expires_in = body.expires_in;
      res.json({
          'access_token': access_token,
          'refresh_token': refresh_token,
          'expires_in': expires_in
      });
    }
  })      
})

But i am still getting the error 400 and i can't seem to figure it out. Please i would really appreciate a response. Here is how i handled the code on my react native application

const [request2, response2, promptAsync2] = useAuthRequest({
        clientId: clientID,
        clientSecret: clientSecret,
        scopes: [
            'user-read-playback-state',
            'user-modify-playback-state',
            'user-read-currently-playing',
            'streaming',
            'playlist-read-private',
            'playlist-read-collaborative',
            'playlist-modify-private',
            'playlist-modify-public',
            'user-follow-modify',
            'user-follow-read',
            'user-read-playback-position',
            'user-library-modify',
            'user-library-read',
            'user-read-email',
            'user-read-private'
        ],
        usePKCE: false,
        redirectUri: makeRedirectUri({
            scheme: undefined
        })
    },
        discovery
    )
    useEffect(() => {
        if (response2?.type === 'success') {
            // get spotify access code
             const { code } = response2.params;
            const getSpotifyCode = async() =>{    
                const code2 = {
                    code
                }
                await axios.post('http://localhost:8005/get-spotify-access-code', code2).then(
                response =>{
                    console.log(response);
                }
               ).catch(error =>{
                console.log(error)
               })
            }
           getSpotifyCode()
        }
    }, [response2])


Solution

  • You should be match same port

    Between running Expo port and Redirect URI of port in Spotify Developer Dashboard.

    My redirect URL port is 3000

    https://developer.spotify.com/dashboard
    

    enter image description here

    Expo running port is 3000

    In package.json

      "scripts": {
        "start": "expo start --port 3000",
        "android": "expo start --android --port 3000",
        "ios": "expo start --ios --port 3000",
        "web": "expo start --web --port 3000"
      },
    

    Demo code

    App.js

    import * as React from 'react';
    import * as WebBrowser from 'expo-web-browser';
    import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
    import { Button, View, Text, StyleSheet } from 'react-native';
    import axios from 'axios';
    
    WebBrowser.maybeCompleteAuthSession();
    
    // Endpoint
    const discovery = {
      authorizationEndpoint: 'https://accounts.spotify.com/authorize',
      tokenEndpoint: 'https://accounts.spotify.com/api/token'
    };
    
    const PORT = 3000; // Corrected: PORT should not be part of the config object
    const CLIENT_ID = '<your client id>';
    const CLIENT_SECRET = '<your client secret>';
    const REDIRECT_URI = `http://localhost:${PORT}/callback`; // your redirect URI
    
    export default function App() {
      const [request, response, promptAsync] = useAuthRequest(
        {
          clientId: CLIENT_ID,
          clientSecret: CLIENT_SECRET,
          scopes: ['user-read-email', 'playlist-modify-public'],
          usePKCE: false,
          redirectUri: REDIRECT_URI,
        },
        discovery
      );
    
      const [accessToken, setAccessToken] = React.useState("mockAccessToken");
      const [refreshToken, setRefreshToken] = React.useState("mockRefreshToken");
    
      React.useEffect(() => {
        if (response?.type === 'success') {
          const { code } = response.params;
          // Exchange code for access token and refresh token
          axios.post(
            'https://accounts.spotify.com/api/token',
            new URLSearchParams({
              'grant_type': 'authorization_code',
              'redirect_uri': REDIRECT_URI,
              'code': code
            }).toString(),
            {
              headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
              },
              auth: {
                username: CLIENT_ID,
                password: CLIENT_SECRET
              }
            }
          )
            .then((response) => {
              setAccessToken(response.data.access_token);
              setRefreshToken(response.data.refresh_token);
            })
            .catch((error) => {
              console.error('Error exchanging code for token:', error);
            });
        }
      }, [response]);
    
    
      return (
        <View style={styles.container}>
          <Button
            disabled={!request}
            title="Login"
            onPress={() => {
              promptAsync();
            }}
          />
          {accessToken && (
            <Text style={styles.tokenText}>Access Token: {accessToken}</Text>
          )}
          {refreshToken && (
            <Text style={styles.tokenText}>Refresh Token: {refreshToken}</Text>
          )}
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#fff',
      },
      tokenText: {
        marginBottom: 20,
        fontSize: 16,
      },
    });
    

    package.json

    {
      "main": "node_modules/expo/AppEntry.js",
      "scripts": {
        "start": "expo start --port 3000",
        "android": "expo start --android --port 3000",
        "ios": "expo start --ios --port 3000",
        "web": "expo start --web --port 3000"
      },
      "dependencies": {
        "@expo/metro-runtime": "~3.1.3",
        "@expo/vector-icons": "^14.0.0",
        "axios": "^1.6.8",
        "expo": "~50.0.14",
        "expo-auth-session": "~5.4.0",
        "expo-status-bar": "~1.11.1",
        "expo-web-browser": "~12.8.2",
        "react": "18.2.0",
        "react-dom": "18.2.0",
        "react-native": "0.73.6",
        "react-native-paper": "4.9.2",
        "react-native-web": "~0.19.6"
      }
    }
    

    Install dependencies

    npm install
    

    Run it

    npm run web
    

    Result

    enter image description here