passport-spotify accessToken undefined in session

40 Views Asked by At

This is an expo project that I'm working with on web. It attempts to authenticate with Spotify Web API. The access token is returned from the Spotify authentication page and is available inside the callback on req.user.accessToken.

However, in other routes called subsequently the req.user.accessToken and req.session.accessToken are both undefined. This is despite the attempt made inside the callback to assign the token to the session.

Here, the access token is available inside the authentication callback...

app.get(
"/auth/spotify/callback",
  passport.authenticate("spotify", { failureRedirect: "/" }),
  (req, res) => {
    req.session.accessToken = req.user.accessToken;
    console.log("callback: " + req.session.accessToken);
    res.redirect("http://localhost:8081");
  }
);

But not in other routes

app.get("/spotify/get-currently-playing", async (req, res) => {
  try {
    const accessToken = req.session && req.session.accessToken;
    if (!accessToken) {
      throw new Error("Access token not found");
    }
    const userData = await getCurrentSongInfo(accessToken);
    res.json(userData);
...

The middleware is setup earlier on as follows

app.use(
  session({
    secret: "x",
    resave: false,
    saveUninitialized: false,
  })
);

app.use(passport.initialize());
app.use(passport.session());

passport.use(
  new SpotifyStrategy(
    {
      clientID: "x",
      clientSecret: "x",
      callbackURL: "http://localhost:5001/auth/spotify/callback",
    },
    (accessToken, refreshToken, expires_in, profile, done) => {
      // Store the access token in the session
      if (accessToken) {
        profile.accessToken = accessToken;
        console.log(profile);
        done(null, profile);
      } else {
        done(new Error("Failed to retrieve access token"));
      }
    }
  )
);
1

There are 1 best solutions below

0
Bench Vue On

You need to get from user session data

FROM

app.get(
"/auth/spotify/callback",
  passport.authenticate("spotify", { failureRedirect: "/" }),
  (req, res) => {
    req.session.accessToken = req.user.accessToken;
    console.log("callback: " + req.session.accessToken);
    res.redirect("http://localhost:8081");
  }
);

TO

app.get(
"/auth/spotify/callback",
  passport.authenticate("spotify", { failureRedirect: "/" }),
  (req, res) => {
    if (req.user && req.user.accessToken) {
        console.log("callback: " + req.user.accessToken);
    } else {
        console.log('callback: accessToken is none');
    }
    res.redirect("http://localhost:8081");
  }
);

Other routes also same method of access

req.user.accessToken

Demo code

package.json

{
  "dependencies": {
    "consolidate": "^1.0.3",
    "cors": "^2.8.5",
    "express": "^4.19.1",
    "express-session": "^1.18.0",
    "nunjucks": "^3.2.4",
    "passport": "^0.7.0",
    "passport-spotify": "^2.0.0"
  }
}

File tree

enter image description here

How to get

client_id, client_secret and redirect_uri

Open URL by browser

https://developer.spotify.com/dashboard

enter image description here

Save as server.js with your Client ID/secret and redirect URI.

const express = require('express');
const cors = require('cors');
const session = require('express-session');
const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
const consolidate = require('consolidate');

const CLIENT_ID = '<your client id>';
const CLIENT_SECRET = '<your client secret>';
const port = 5001; // make sure your REDIRECT_URI's port
const authCallbackPath = '/auth/spotify/callback';

const REDIRECT_URI = `http://localhost:${port}${authCallbackPath}`;


passport.serializeUser(function (user, done) {
    done(null, user);
});

passport.deserializeUser(function (obj, done) {
    done(null, obj);
});

passport.use(
    new SpotifyStrategy(
        {
            clientID: CLIENT_ID,
            clientSecret: CLIENT_SECRET,
            callbackURL: REDIRECT_URI,
        },
        function (accessToken, refreshToken, expires_in, profile, done) {
            // asynchronous verification, for effect...
            process.nextTick(function () {
                try {
                    profile.accessToken = accessToken;
                    console.log(`accessToken is ${profile.accessToken}`);
                    return done(null, profile);
                } catch (error) {
                    return done(error);
                }
            });
        }
    )
);
const app = express();
app.use(cors()); // Enable CORS

app.use(
    session({ secret: 'keyboard cat', resave: false, saveUninitialized: false })
);

// Initialize Passport!  Also use passport.session() middleware, to support
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(__dirname + '/public'));

app.engine('html', consolidate.nunjucks);

app.get('/', function (req, res) {
    res.render('index.html', { user: req.user });
});

app.get('/account', ensureAuthenticated, function (req, res) {
    if (req.user && req.user.accessToken) {
        console.log(`In account accessToken is ${req.user.accessToken}`);
    } else {
        console.log('In account accessToken is none');
    }
    res.render('account.html', { user: req.user });
});

app.get('/login', function (req, res) {
    res.render('login.html', { user: req.user });
});

app.get('/logout', function (req, res) {
    req.logout();
    res.redirect('/');
});

// GET /auth/spotify
app.get(
    '/auth/spotify',
    passport.authenticate('spotify', {
        scope: ['user-read-email', 'user-read-private'], // you can add more scopes it depends on your require APIs call
        showDialog: false, // if set true force shows login UI
    })
);

// GET /callback
app.get(
    authCallbackPath,
    passport.authenticate('spotify', { failureRedirect: '/login' }),
    function (req, res) {
        res.redirect('/');
    }
);

// Logout route. it has a defect, I think `passport-spotify` not address it.
app.get('/logout', function (req, res) {
    req.logout(); // No need for a callback function here
    res.redirect('/'); // Redirect to login page or any desired page
});

app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

function ensureAuthenticated(req, res, next) {
    if (req.isAuthenticated()) {
        return next();
    }
    res.redirect('/login');
}

HTML files

account.html

{% extends 'layout.html' %} {% block content %}
<p>ID: {{ user.id }}</p>
<p>Token: {{ user.accessToken }}</p>
<p>Profile:
    <a href=" {{ user.profileUrl }}">{{ user.profileUrl }}</a>
</p>
{% endblock %}

index.html

{% extends 'layout.html' %}

{% block content %}
{% if not user %}
    <h2>Welcome! Please log in.</h2>
    <p><a href="/login">login</a></p>
{% else %}
    <h2>Hello, {{ user.username }}.</h2>
    <p>Your email is {{ user.emails[0].value }}</p>
{% endif %}
{% endblock %}

layout.html

<!DOCTYPE html>
<html>

<head>
    <title>Passport-Spotify Example</title>
</head>

<body>
    {% if not user %}
    <p>
        <a href="/">Home</a> |
        <a href="/login">Log In</a>
    </p>
    {% else %}
    <p>
        <a href="/">Home</a> |
        <a href="/account">Account</a> |
        <a href="/logout">Log Out</a>
    </p>
    {% endif %} {% block content %}{% endblock %}
</body>

</html>

login.html

{% extends 'layout.html' %} {% block content %}
<a href="/auth/spotify">Login with Spotify</a>
{% endblock %}

Result

enter image description here

In server terminal

enter image description here