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);
}
// 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:and used it at callback route:
Now, when Twitter calls the callback route, it gets into
TwitterAuthGuardhandleRequestmethod.If the access was granted,
userparameter contains data from user profile and is passed further down the chain toTwitterGuardvalidatemethod (see above in the question).If the access was denied then
userparameter isfalse.Therefore in the controller callback route method I get either normalized user data or
falseinuserparameter therefore I can check whether it failed or not and act accordingly.