简体   繁体   中英

Social Login in NestJS using @AuthGuard, Passport

So, I have almost finished attempt to implement social login in NestJS powered app. I have some problems though:

First things first. I have AuthModule and in there is provider TwitterGuard :

const twitterOptions: IStrategyOptionWithRequest = {
  consumerKey: process.env[ENV.SOCIAL.TWITTER_CONSUMER_KEY],
  consumerSecret: process.env[ENV.SOCIAL.TWITTER_CONSUMER_SECRET],
  callbackURL: process.env[ENV.SOCIAL.TWITTER_CALLBACK_URL],
  passReqToCallback: true,
  includeEmail: true,
  skipExtendedUserProfile: false,
};

export class TwitterGuard extends PassportStrategy(Strategy, 'twitter') {
  constructor() {
    super(twitterOptions);
  }

  /*  authenticate(req: Request, options?: any): void {
      console.log(req.body);
      // super.(req, options);
    }*/

  // Magical nest implementation, eq to passport.authenticate
  validate(req: Request, accessToken: string, refreshToken: string, profile: Profile, done: (error: any, user?: any) => void) {
    const user: SocialAuthUser = {
      id: profile.id,
      nick: profile.username,
      name: profile.displayName,
    };
    if (profile.emails) {
      user.email = profile.emails.shift().value;
    }
    if (profile.photos) {
      user.avatar = profile.photos.shift().value;
    }

    done(null, user);
  }
}

as well as AuthController :

@Controller('auth')
@ApiUseTags('auth')
export class SocialAuthController {

  constructor(private us: UserService) {
  }

  @Get('twitter')
  @UseGuards(AuthGuard('twitter'))
  twitter() {
    throw new UnauthorizedException();
  }

  @Get('twitter/callback')
  @UseGuards(AuthGuard('twitter'))
  async twitterCallback(@ReqUser() socialUser: SocialAuthUser, @Res() response) {
    const user = await this.us.registerSocialUser(socialUser);
    if (user) {
      // console.log('Redirect', '/some-client-route/token');
      response.redirect(`${SITE_URL}/activate/${user.token}`);
    }
    response.sendStatus(401);
  }

}

When I am calling URL /auth/twitter the guard kicks in and reroutes to Twitter page asking user to grant access to Twitter app.

If the user grants access, everything is fine, on the callback route ( /auth/twitter/callback ) the TwitterGuard kicks in again and processes user in validate , stores to request and I can access that further in controller. So far so good.

However if user denies access to Twitter app, the guard returns 401 on the callback route even before any of my methods are hit.

I tried to play with authenticate method that is called (now commented out in the code) where I could somehow maybe tweak this but have no idea what to return or do. If that is a way to go, how do I redirect from there to twitter auth page like passport strategy does? What to return on callback to keep going and set some flag that access was denied?

Is there any other way to do it? What am I missing?

Thanks in advance.

Edit: If you have questions what does @ReqUser() do, here it is:

export const ReqUser = createParamDecorator((data, req): any => {
  return req.user;
});

Nevermind, I found a solution, this answer helped a lot. Posting here in case someone else would get into the same trouble.

I created TwitterAuthGuard :

export class TwitterAuthGuard extends AuthGuard('twitter') {
  handleRequest(err, user, info, context) {
    return user;
  }
}

and used it at callback route:

  @Get('twitter/callback')
  @UseGuards(TwitterAuthGuard)
  async twitterCallback(@ReqUser() socialUser: SocialAuthUser, @Res() response) {
    if (socialUser) {
      const user = await this.us.registerSocialUser(socialUser);
      if (user) {
        response.redirect(`...url`);
        return;
      }
    }
    response.redirect(SocialAuthController.authFailedUrl(LoginMethod.TWITTER));
  }

Now, when Twitter calls the callback route, it gets into TwitterAuthGuard handleRequest method.

If the access was granted, user parameter contains data from user profile and is passed further down the chain to TwitterGuard validate method (see above in the question).

If the access was denied then user parameter is false .

Therefore in the controller callback route method I get either normalized user data or false in user parameter therefore I can check whether it failed or not and act accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM