Nodejs: Nest ile Google Oauth Implementasyonu

}

Giri┼č

Hepinize Merhabalar­čĹő
Bu yaz─▒m─▒zda discord, google, facebook ve di─čer sosyal platformdaki hesaplar─▒n ┼čifrelerini istemeden, bize sunulan oauth servisleri ile kullan─▒c─▒ bilgilerini ├žekece─čiz.

Ben bu yaz─▒da sadece google'i anlatmaya ├žal─▒┼čaca─č─▒m.
├çok fazla detaya inmeyece─čim, dilerseniz kaynak├ža k─▒sm─▒ndan derine inebilirsiniz.

Direkt olarak projeyi github ├╝zerinden incelemek i├žin buraya, test etmek i├žinde buraya
t─▒klayabilirsiniz.


Kullanaca─č─▒m─▒z K├╝t├╝phane Ve Servisler

  1. nestjs
  2. passport-google-oauth20

Database se├žimi:

  1. mongodb
    Not: E─čer mongodb uzak sunucu ba─člant─▒s─▒n─▒ nas─▒l alaca─č─▒n─▒z─▒ bilmiyorsan─▒z, buraya t─▒klayarak ilgili yaz─▒ya ula┼čabilirsiniz.

Google Oauth Servislerinin Aktif Edilmesi

  1. Google Console Cloud'a giri┼č yap─▒yoruz.(Buraya t─▒klayarak h─▒zl─▒ca gidebilirsiniz.)
  2. Proje se├žimini yap─▒yoruz.
  3. Sol men├╝den Credentials'a ard─▒ndan, Create Credentials'a hemen ard─▒ndan ise OAuth client ID butonlar─▒na t─▒kl─▒yoruz.
    Screenshot_121
  4. Gerekli ayarlar─▒ yapt─▒ktan sonra, Authorized redirect URIs k─▒sm─▒na yetkilendirmek istedi─čimiz linkleri yaz─▒yoruz. Buras─▒ en ├Ânemli k─▒s─▒mlardan birisi oldu─ču i├žin dikkat ediniz :)
    Screenshot_123
  5. Ve :tada: API key ve secret keylerimiz haz─▒r. Bunlar bize ileride laz─▒m olaca─č─▒ i├žin sa─ča sola bir kenara kaydedelim.
    Screenshot_122
  6. Ayarlamalar─▒ yapmaya devam edelim, soldaki men├╝den OAuth consent screen b├Âl├╝m├╝ne ge├želim.
    Screenshot_124
  7. Buray─▒ da ge├žtikten sonra, ├Ân├╝m├╝ze gelen paneldeki gerekli olan inputlar─▒ doldural─▒m.
    Scopes k─▒sm─▒na geldi─čimizde, kullan─▒c─▒n─▒n hangi verilerini ├žekmek istedi─čimiz bilgisini isteyecek. Bana email ve baz─▒ ki┼čisel bilgileri laz─▒m oldu─ču i├žin g├Ârseldeki gibi i┼čaretliyorum.
    Screenshot_125
  8. Hepsini tamamlad─▒ktan sonra, uygulamay─▒ yay─▒na alal─▒m ve google cloud ile i┼čimizi tamamen bitirelim!
    Screenshot_126

Backendin kurulmas─▒

Yaz─▒da da belirtti─čim gibi bu projede nestjs kullanaca─č─▒m.
Yeni bir proje a├ž─▒p ard─▒ndan hemen konsola ge├žiyorum.

Ard─▒ndan nest-cli kurulu de─čilse onu kuruyorum.

npm install -g @nestjs/cli

CLI'i kullanarak yeni bir nest uygulamas─▒ yarat─▒yorum.

nest new .

┼×imdi de passport-google-oauth20 k├╝t├╝phanesini y├╝kleyelim.

npm install --save @nestjs/passport passport passport-google-oauth20
npm install -D @types/passport-google-oauth20

Objeleri DTO'a ├ževirmeye i┼če yarayan plainToClass fonksiyonunu kullanmak i├žin class-transformer k├╝t├╝phanesini kural─▒m.

npm install class-transformer --save  

Vee projemiz haz─▒r..
Screenshot_127

Hemen ard─▒ndan projeyi aya─ča kald─▒r─▒yorum.

npm run start:dev

Screenshot_128
Bu ekran─▒ g├Âr├╝yorsan─▒z s─▒k─▒nt─▒ yok demektir, devam edebilirsiniz :)


Typeorm ─░le Mongodb'nin Entegre Edilmesi

Projeye typeorm ve mongodb paketlerini y├╝kl├╝yorum.

npm install @nestjs/typeorm typeorm [email protected] --save

** [email protected] > ├╝zeri s├╝r├╝mlerinde ger├žekle┼čen b├╝y├╝k de─či┼čikliklerden dolay─▒, "Right-hand side of 'instanceof' is not an object" gibi olu┼čabilecek hatalar─▒ ├Ânlemek i├žin [email protected] s├╝r├╝m├╝ne d├╝┼č├╝r├╝ld├╝. **

//app.module.ts
...
import { TypeOrmModule } from '@nestjs/typeorm';
...
//app.module.ts
...
imports: [
    TypeOrmModule.forRoot({
      type: 'mongodb',
      url: 'mongodb+srv://username:[email protected]/databasename?retryWrites=true&w=majority',
      authSource: 'admin',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      ssl: true,
      useUnifiedTopology: true,
      useNewUrlParser: true,
      synchronize: true,
      logging: true,
    }),
  ],
...

Kullan─▒c─▒ Mod├╝l├╝n├╝n Olu┼čturulmas─▒

Terminalden k├Âk dizine geliyoruz ve hemen ard─▒ndan a┼ča─č─▒daki kodu ├žal─▒┼čt─▒r─▒yoruz.

nest g resource user
//REST API [x]
//CRUD [x]

Yeni eklenen dosyalar;
Screenshot_129

Entity ve DTO'nun d├╝zenlenmesi

Ad─▒, soyad─▒ ve email adresinden olu┼čan bir kullan─▒c─▒ tablosu haz─▒rlayaca─č─▒m.

//user.entity.ts
import { Column, Entity, ObjectID, ObjectIdColumn } from 'typeorm';

@Entity('User')
export class User {
  @ObjectIdColumn()
  id: ObjectID;

  @Column()
  name: string;

  @Column()
  surname: string;

  @Column({ unique: true })
  email: string;
}
//create-user.dto
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class CreateUserDto {
  @Expose()
  public name: string;

  @Expose()
  public surname: string;

  @Expose()
  public email: string;
}
//user.module.ts
...
imports: [TypeOrmModule.forFeature([User])],
...
//user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private userRepository: Repository<User>,
  ) {}
  async create(createUserDto: CreateUserDto) {
    return await this.userRepository.create(createUserDto);
  }

  async findOne(data: object) {
    return await this.userRepository.findOne(data);
  }
}
//user/guards/google-auth-guard.ts
import { HttpException, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
  constructor() {
    super();
  }

  handleRequest(err: any, user: any, info: any, context: any, status: any) {
    if (err || !user) {
      throw new HttpException(err.message, err.status);
    }
    return user;
  }
}
//user/strategies/google.strategy.ts
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '@nestjs/common';
import { UserService } from '../user.service';
import { PassportStrategy } from '@nestjs/passport';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(private userService: UserService) {
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      callbackURL: `http://localhost:3000/auth/google`,
      scope: ['email', 'profile'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails } = profile;
    const email = emails[0].value;

    const newUser = plainToClass(CreateUserDto, {
      email,
      name: name.givenName,
      surname: name.familyName,
    });

    const foundedUser = await this.userService.findOne({ email });

    if (foundedUser) return done(null, foundedUser);
    else {
      const savedUser = await this.userService.create(newUser);
      return done(null, savedUser);
    }
  }
}

clientId: Yukar─▒da google'dan ald─▒─č─▒m─▒z clientId,
clientSecret: Yukar─▒da google'dan ald─▒─č─▒m─▒z clientSecret,
callbackURL: Authorized redirect URI k─▒sm─▒na tan─▒mlad─▒─č─▒m─▒z URL,
scoped: Kullan─▒c─▒n─▒n ├žekmek istedi─čimiz verileri..

Son olu┼čturdu─čumuz google.strategy.ts dosyas─▒n─▒, user.module.ts'e providers olarak dahil edelim.

//user.module.ts
...
providers: [UserService, GoogleStrategy],
...

Ve t├╝m i┼člemler tamamland─▒!


Google Oauth Servisinin Kullan─▒lmas─▒

┼×imdi de bu servisi nas─▒l kullanabiliriz gelin ona bakal─▒m.

localhost:3000/auth/google adresine istek att─▒─č─▒m─▒zda, login de─čilsek direkt olarak google auth panel gelecektir ve otomatik olarak callbackUrl'de tan─▒mlad─▒─č─▒m─▒z linke y├Ânlenecektir. T├╝m bu y├Ânlendirme i┼člemlerini kulland─▒─č─▒m─▒z passport-google-oauth20 k├╝t├╝phanesini bizim yerimize yap─▒yor.

callbackUrl'e gelirken code parametresiyle d├Ânecektir. Tabi bunlar─▒ kendi i├žinde yapt─▒─č─▒ i├žin uzaktan bak─▒nca servisin nas─▒l ├žal─▒┼čt─▒─č─▒n─▒ anlamak bir hayli zorla┼č─▒yor.

Gelin bu zorlu─ču biraz a┼čmaya ├žal─▒┼čal─▒m.
localhost:3000/auth/google adresine istek att─▒─č─▒m─▒zda bizi hesap se├žmemiz i├žin ba┼čka bir linke y├Ânlendiriyor. Bu y├Ânlendi─čimiz sayfay─▒ manuel olarak kendimiz yazacak olsayd─▒k;

https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&response_type=code&redirect_uri=http://localhost:3000/auth/google&client_id=clientId

clientId: Yukar─▒da google'dan ald─▒─č─▒m─▒z clientId.

Yukar─▒daki linke query olarak ge├žirdi─čimiz redirect_uri'nin backend ve google cloud'da Authorized redirect URI k─▒sm─▒yla e┼čle┼čiyor oldu─čuna dikkat edelim.
At─▒lan bu istekte, ├Ânce hesap se├žilecek ard─▒nda bizi redirect_uri'e code parametresini ekleyerek otomatik olarak y├Ânlendirecektir ve direkt olarak code'i ├ž├Âz├╝p bilgileri database'e kaydedip, kaydetti─či verileri tekrar bize d├Ânecektir.


Son

Okudu─čunuz i├žin ├žok te┼čekk├╝rler­čÜÇ
Kucak dolusu sevgilerle!


Merakl─▒s─▒ ─░├žin Kaynak├ža

Using OAuth 2.0 to Access Google APIs | Google Identity
MongoDB
Cast entity to dto | Newbedev
So based on JesseÔÇÖs awesome answer I ended up creating the DTO using @Exclude() and @Expose() to remove all but exposed properties: import { IsString, IsEmail }
How to add a FREE MongoDB database to your NestJS API with TypeORM
Need a free and performant database for your NestJS app? Learn how you can easily integrate Azure Cosmos DB using TypeORM and the MongoDB driver with this hands-on tutorial.
Tolga ├ça─člayan

Tolga ├ça─člayan

En tehlikeli kelime nedir Olric? -AmaÔÇÖd─▒r efendim bana g├Âre. Neden Olric? -├ľnceden s├Âylenen her s├Âylemi veya kelimeyi ├Âld├╝r├╝r! Mesela, seni seviyorum ama. gibi.
Anonim