sahat / hackathon-starter

A boilerplate for Node.js web applications
MIT License
34.89k stars 8.18k forks source link

Create a custom provider #1042

Closed Tilican closed 4 years ago

Tilican commented 4 years ago

Hi, I want to create a Twitch login but i can't make it works ...

This is my custom oauth for twitch

/**
 * Module dependencies.
 */
const util = require('util');
const OAuth2Strategy = require('passport-oauth2');
const { InternalOAuthError } = require('passport-oauth2');

/**
 * `Strategy` constructor.
 *
 * The Twitch authentication strategy authenticates requests by delegating to
 * Twitch using the OAuth 2.0 protocol.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`,
 * `refreshToken` and service-specific `profile`, and then calls the `done`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `clientID`      your Twitch application"s client id
 *   - `clientSecret`  your Twitch application"s client secret
 *   - `callbackURL`   URL to which Twitch will redirect the user after granting authorization
 *
 * Examples:
 *
 *     passport.use(new TwitchStrategy({
 *         clientID: "123-456-789",
 *         clientSecret: "shhh-its-a-secret"
 *         callbackURL: "https://www.example.net/auth/twitch/callback"
 *       },
 *       function(accessToken, refreshToken, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://id.twitch.tv/oauth2/authorize';
  options.tokenURL = options.tokenURL || 'https://id.twitch.tv/oauth2/token';

  OAuth2Strategy.call(this, options, verify);
  this.name = 'twitch';

  this._oauth2.setAuthMethod('Bearer');
  this._oauth2.useAuthorizationHeaderforGET(true);
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Retrieve user profile from Twitch.
 *
 * This function constructs a normalized profile, with the following properties:
 *
 *   - `provider`         always set to `twitch`
 *   - `id`
 *   - `username`
 *   - `displayName`
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
// eslint-disable-next-line func-names
Strategy.prototype.userProfile = function (accessToken, done) {
  this._oauth2.get('https://api.twitch.tv/helix/users', accessToken, (err, body, res) => {
    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      const json = JSON.parse(body);

      const profile = { provider: 'twitch' };
      profile.id = json.data[0].id;
      profile.userName = json.data[0].login;
      profile.email = json.data[0].email;
      profile.displayName = json.data[0].display_name;
      profile.profileImageUrl = json.data[0].profile_image_url;
      profile.viewCount = json.data[0].view_count;

      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch (e) {
      done(e);
    }
  });
};

/**
 * Return extra parameters to be included in the authorization request.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
// eslint-disable-next-line func-names
Strategy.prototype.authorizationParams = function (options) {
  const params = {};
  if (typeof options.forceVerify !== 'undefined') {
    params.force_verify = !!options.forceVerify;
  }
  return params;
};
/**
 * Expose `Strategy`.
 */
module.exports = Strategy;

I import this custom oauth and create a passport strategy with in passport.js

/**
 * Twitch API OAuth.
 */
passport.use(new Strategy({
  clientID: process.env.TWITCH_CLIENT_ID,
  clientSecret: process.env.TWITCH_CLIENT_SECRET,
  callbackURL: `${process.env.BASE_URL}/auth/twitch/callback`,
  scope: ['user_read', 'chat:read', 'chat:edit', 'whispers:read', 'whispers:edit', 'user:read:email'],
  passReqToCallback: true
},
(req, accessToken, refreshToken, params, profile, done) => {
  console.log({ accessToken, refreshToken, params, profile });
  if (req.user) {
    User.findOne({ twitch: profile.id }, (err, existingUser) => {
      if (existingUser) {
        req.flash('errors', { msg: 'There is already a Twitch account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
        done(err);
      } else {
        User.findById(req.user.id, (err, user) => {
          if (err) { return done(err); }
          user.twitch = profile.id;
          user.email = profile.email;
          user.tokens.push({
            kind: 'twitch',
            accessToken,
            refreshToken,
            accessTokenExpires: moment().add(params.expires_in, 'seconds').format()
          });
          user.profile.name = user.profile.name || profile.userName || profile.displayName;
          user.profile.picture = user.profile.picture || profile.profileImageUrl;

          user.save((err) => {
            req.flash('info', { msg: 'Twitch account has been linked.' });
            done(err, user);
          });
        });
      }
    });
  } else {
    User.findOne({ twitch: profile.id }, (err, existingUser) => {
      if (err) { return done(err); }
      if (existingUser) {
        return done(null, existingUser);
      }
      User.findOne({ email: profile.email }, (err, existingEmailUser) => {
        if (err) { return done(err); }
        if (existingEmailUser) {
          req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' });
          done(err);
        } else {
          const user = new User();
          user.twitch = profile.id;
          user.email = profile.email;
          user.tokens.push({
            kind: 'twitch',
            accessToken,
            refreshToken,
            accessTokenExpires: moment().add(params.expires_in, 'seconds').format()
          });
          user.profile.name = user.profile.name || profile.userName || profile.displayName;
          user.profile.picture = user.profile.picture || profile.profileImageUrl;
          user.save((err) => {
            done(err, user);
          });
        }
      });
    });
  }
}));

I add some links in app.js

/**
 * OAuth authorization routes. (API examples)
 */
app.get('/auth/twitch', passport.authorize('twitch', {}));
app.get('/auth/twitch/callback', passport.authorize('twitch', { failureRedirect: '/login' }), (req, res) => {
  res.redirect(req.session.returnTo);
});

I add this in login.pug

      .offset-md-3.col-md-7.pl-2
        a.btn.btn-block.btn-snapchat.btn-social(href='/auth/twitch')
          i.fab.fa-snapchat-ghost.fa-sm
          | Sign in with Twitch

Everything is OK, i can login, everything is in database but i never got a session ! If i create an account with email it works correctly ...

What i have missed ?

YasharF commented 4 years ago

What do you mean by

Everything is OK, i can login, everything is in database but i never got a session

Is there an error message that you are getting during the process, or when you are trying to do something afterward?

Tilican commented 4 years ago

@YasharF no error, but i connect, i got redirected to '/' and i'm not connected, i can't manage my account etc ...

I got every data on mongoDb, email, twitch picture etc ... But i can't connect it's weird

But if i do it with email/ password on another account it works !

YasharF commented 4 years ago

I don't understand what you mean by

i can't connect

Tilican commented 4 years ago

Gif will explain @YasharF

When i login with local local

When i login with twitch ( i already accept oauth connection ) twitch

Database image

Tilican commented 4 years ago

i got it ...

in app.js

/**
 * OAuth authorization routes. (API examples)
 */
app.get('/auth/twitch', passport.authorize('twitch', {}));
app.get('/auth/twitch/callback', passport.authorize('twitch', { failureRedirect: '/login' }), (req, res) => {
  res.redirect(req.session.returnTo);
});

should be

/**
 * OAuth authorization routes. (API examples)
 */
app.get('/auth/twitch', passport.authenticate('twitch', {}));
app.get('/auth/twitch/callback', passport.authenticate('twitch', { failureRedirect: '/login' }), (req, res) => {
  res.redirect(req.session.returnTo || '/');
});
YasharF commented 4 years ago

In case if you are still working on this, I added twitch support with https://github.com/sahat/hackathon-starter/commit/c8df1150075b6e32129a9974975529e2e7a2dc37 and https://github.com/sahat/hackathon-starter/commit/901140f3b8949ea79029bab1cf1bf706e8ef2a7c