Google OAuth / Social Login
Muammodan boshlaylik
Foydalanuvchi "Google bilan kirish" tugmasini bosdi. Qanday ishlaydi? Siz Google parolini so'ramaysiz — Google o'zi tekshiradi. Muvaffaqiyatli bo'lsa, Google sizga foydalanuvchi ma'lumotlarini yuboradi. Siz esa token yaratasiz.
Bu — OAuth 2.0. Uchinchi tomon (Google) autentifikatsiyani o'z zimmasiga oladi, siz faqat natijadan foydalanasiz.
OAuth 2.0 oqimi
1. Foydalanuvchi "Google bilan kiring" bosadi
│
▼
2. Backend Google ga yo'naltiradi
https://accounts.google.com/o/oauth2/auth?client_id=...
│
▼
3. Foydalanuvchi Google da login qiladi, ruxsat beradi
│
▼
4. Google sizning callback URL ga code yuboradi
GET /auth/google/callback?code=4/P7q7W91...
│
▼
5. Backend code → Google token oladi
│
▼
6. Google token → foydalanuvchi profili (email, name, photo)
│
▼
7. Bazada foydalanuvchini topasiz yoki yaratasiz
│
▼
8. O'z JWT tokeningizni yaratasiz, qaytarasiz
Hayotiy o'xshatma: yangi ofisga kirishda qo'riqchi (Google) pasportingizni tekshiradi. "Bu odam haqiqiy" degan tasdiqlama beriladi. Ofis esa o'z kirish kartasini (JWT) chiqaradi. Passport bilan emas, kartangiz bilan yurasiz.
Google Console da sozlash
- console.cloud.google.com ga kiring
- Yangi loyiha yarating yoki mavjudini tanlang
- APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Application type: Web application
- Authorized redirect URIs:
http://localhost:3000/auth/google/callback Client IDvaClient Secretni.envga saqlang
# .env
GOOGLE_CLIENT_ID=123456789-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-abcdef123456
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
O'rnatish
npm install passport-google-oauth20
npm install -D @types/passport-google-oauth20
Google Strategiya
// src/auth/strategies/google.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../auth.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private config: ConfigService,
private authService: AuthService,
) {
super({
clientID: config.get('GOOGLE_CLIENT_ID'),
clientSecret: config.get('GOOGLE_CLIENT_SECRET'),
callbackURL: config.get('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'], // ← Google dan nima so'raymiz
});
}
async validate(
accessToken: string, // Google access token — bizga kerak emas
refreshToken: string, // Google refresh token — bizga kerak emas
profile: any, // foydalanuvchi profili
done: VerifyCallback,
) {
const { emails, name, photos } = profile;
const googleUser = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
photo: photos[0].value,
googleId: profile.id,
};
// Bazadan topamiz yoki yaratamiz
const user = await this.authService.findOrCreateGoogleUser(googleUser);
done(null, user); // ← req.user ga qo'yiladi
}
}
scope: ['email', 'profile'] — Google dan faqat email va profil ma'lumotlarini so'raymiz. Boshqa ruxsatlar (calendar, drive) kerak bo'lsa shu yerga qo'shiladi.
AuthService — Google foydalanuvchini topish yoki yaratish
// src/auth/auth.service.ts
async findOrCreateGoogleUser(googleUser: {
email: string;
firstName: string;
lastName: string;
photo: string;
googleId: string;
}) {
// Avval email bo'yicha topamiz
let user = await this.usersService.findByEmail(googleUser.email);
if (!user) {
// Yo'q bo'lsa — yangisini yaratamiz
user = await this.usersService.createGoogleUser({
name: `${googleUser.firstName} ${googleUser.lastName}`,
email: googleUser.email,
photo: googleUser.photo,
googleId: googleUser.googleId,
// parol yo'q — Google foydalanuvchi parol bilan kira olmaydi
});
}
return user;
}
User sxemasiga Google maydonlari
// user.schema.ts
@Schema({ timestamps: true })
export class User {
@Prop({ required: true })
name: string;
@Prop({ required: true, unique: true })
email: string;
@Prop({ select: false, default: null })
password: string | null; // Google foydalanuvchilarda null
@Prop({ default: null })
googleId: string | null;
@Prop({ default: null })
photo: string | null;
@Prop({ default: 'local', enum: ['local', 'google'] })
provider: string; // qaysi usulda ro'yxatdan o'tgan
}
Guard va Controller
// src/auth/guards/google-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {}
// src/auth/auth.controller.ts
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// GET /auth/google — Google ga yo'naltirish
@Get('google')
@Public()
@UseGuards(GoogleAuthGuard)
googleLogin() {
// Bu metod hech narsa qilmaydi
// GoogleAuthGuard foydalanuvchini Google ga yo'naltiradi
}
// GET /auth/google/callback — Google qayta yo'naltiradi
@Get('google/callback')
@Public()
@UseGuards(GoogleAuthGuard)
async googleCallback(@Req() req, @Res() res) {
// req.user — GoogleStrategy.validate() dan kelgan
const tokens = await this.authService.login(req.user);
// Frontend ga token bilan yo'naltirish
const frontendUrl = process.env.FRONTEND_URL;
res.redirect(`${frontendUrl}/auth/callback?token=${tokens.accessToken}`);
}
}
Nima uchun res.redirect? OAuth callback browser orqali keladi. JSON javob qaytarib bo'lmaydi — brauzer uni ko'rsatmaydi. Frontend ga redirect qilib, tokenni URL yoki cookie orqali uzatamiz.
Auth Module ga qo'shish
// src/auth/auth.module.ts
@Module({
imports: [ PassportModule, UsersModule, JwtModule.registerAsync({...}) ],
providers: [
AuthService,
LocalStrategy,
JwtStrategy,
JwtRefreshStrategy,
GoogleStrategy, // ← qo'shildi
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}
Tez-tez uchraydigan xatolar
Callback URL mos kelmaydi
# .env da
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
# Google Console da ham aynan shu URL bo'lishi kerak
# ← bittasi ham boshqacha bo'lsa — "redirect_uri_mismatch" xatosi
Bir xil email, ikki xil provider
// Foydalanuvchi avval parol bilan ro'yxatdan o'tgan,
// keyin Google bilan kirmoqchi bo'ldi — bir xil email
// ❌ Yangi user yaratish — ikkita hisob bir emailda
// ✅ Topib, Google ID ni ulab yangilash
const user = await this.usersService.findByEmail(googleUser.email);
if (user && !user.googleId) {
await this.usersService.linkGoogle(user._id, googleUser.googleId);
}
Callback da token URL da ochiq qoladi
❌ /callback?token=eyJ... — URL loglarida, brauzer tarixida qoladi
✅ httpOnly cookie — xavfsizroq:
res.cookie('accessToken', tokens.accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 15 * 60 * 1000, // 15 daqiqa
});
res.redirect(`${frontendUrl}/dashboard`);
Xulosa
Google OAuth — foydalanuvchi parolini siz bilmaysiz, Google tekshiradi. Muvaffaqiyatli bo'lsa Google profil ma'lumotlarini yuboradi, siz o'z JWT tokeningizni yaratasiz.
Eslab qoling:
- Google Console da callback URL to'g'ri yozilishi shart
findOrCreateGoogleUser— topadi yoki yaratadi, ikki marta yaratmaydiprovidermaydoni —'local'yoki'google'- Bir email, ikki provider — birlashtiring, ikkita hisob yaratmang
- Callback da
res.redirect— JSON qaytarib bo'lmaydi - Token URL da emas, httpOnly cookie da saqlash xavfsizroq
Keyingi dars
29-dars: Serialization — response filtering, parolni yashirish