Reputation: 1340
I implemented Google and Dropbox authentication in my NestJS app within two distinct strategies.
The issue is that I never get a refresh_token along with the access_token. I already tried to remove the app from the already granted apps in my Google/Dropbox account but with no success.
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-google-oauth2';
import { AuthService } from './auth.service';
import { OauthUser } from './oauth-user.entity';
import { UserService } from '../user/user.service';
import { ConfigService } from '../config/config.service';
import { CloudProvider } from '../shared/enums/cloud-service.enum';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private readonly authService: AuthService,
private readonly userService: UserService,
private readonly configService: ConfigService) {
super({
clientID: configService.get('OAUTH_GOOGLE_ID'),
clientSecret: configService.get('OAUTH_GOOGLE_SECRET'),
callbackURL: configService.get('OAUTH_GOOGLE_CALLBACK'),
passReqToCallback: true,
scope: ['email', 'profile', 'https://www.googleapis.com/auth/drive.file'],
accessType: 'offline',
prompt: 'consent',
session: false,
});
}
async validate(request: any, accessToken: string, refreshToken: string, oauthUser: OauthUser) {
console.log('accessToken', accessToken) // <- this one is good
console.log('refreshtoken', refreshToken) // <- always undefined
oauthUser.provider = CloudProvider.GOOGLE;
return this.userService.getOrCreate(oauthUser, accessToken).then(() => {
return this.authService.getJwtForUser(oauthUser).then(jwt => {
return { jwt };
});
});
}
}
Upvotes: 7
Views: 3016
Reputation: 61
I had the same issue when accessing the user's Google Calendar. My problem was that I had controllers like below
@Public()
@Get('google')
@UseGuards(AuthGuard('jwt'))
async googleAuth(): Promise<{ url: string }> {
const url = getCalendarOAuthRequestUrl(this.configService);
return { url };
}
@Get('/google/callback')
@UseGuards(GoogleCalendarGuard)
async googleAuthCallback(@Req() req) {
const { accessToken, refreshToken } = req.user;
// logic...
}
after hours of research and wasted nerves I noticed that my getCalendarOAuthRequestUrl() function that was generating the Google OAuth URL was missing a prompt and access type and after adding them I started to receive a refresh token. Your Google OAuth request URL should look like something like this
https://accounts.google.com/o/oauth2/auth?access_type=offline&prompt=consent&client_id=<YourClientId>&redirect_uri=<YourRedirectUrl>&response_type=code&scope=profile email <AnyGoogleScopeYouNeed>
And in the callback endpoint guard, you will receive a refresh token as well.
Upvotes: 1
Reputation: 91
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { IUserProfile } from './interfaces/user.interface';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CB_URL,
scope: ['email', 'profile'],
});
}
authorizationParams(): { [key: string]: string; } {
return ({
access_type: 'offline'
});
};
async validate(
accessToken: string,
refreshToken: string,
profile: IUserProfile,
done: VerifyCallback,
): Promise<void> {
const { emails } = profile;
console.log(accessToken, refreshToken);
done(null, {email: emails[0].value, accessToken});
}
}
Upvotes: 9