├── .gitignore
├── README.md
├── auth.gif
├── client
├── README.md
└── index.html
└── server
├── .prettierrc
├── README.md
├── nodemon.json
├── package-lock.json
├── package.json
├── src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── auth
│ ├── auth.controller.ts
│ ├── auth.module.ts
│ ├── google.strategy.ts
│ ├── session-auth.middleware.ts
│ ├── strategy-callback.middleware.ts
│ └── strategy.middleware.ts
├── config.ts
├── main.hmr.ts
└── main.ts
├── test
├── app.e2e-spec.ts
└── jest-e2e.json
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | server/node_modules/
2 |
3 |
4 | **/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nest auth
2 |
3 | A simple project using Nestjs for google Authentication.
4 |
5 | ## Article on Medium
6 | [Follow this link to read the doc](https://medium.com/digikare/google-sign-in-authentication-with-nestjs-a6a4bb7de51)
7 |
8 | 
9 |
10 |
--------------------------------------------------------------------------------
/auth.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/workfel/nest-auth/2551756f3d60f115070bcc7a3fe6be061a184390/auth.gif
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # NestJS authentication client
2 |
3 | ## Run
4 |
5 | `$ live-server --port=3000`
6 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Nest authentication By Digikare
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Google sign-in with NestJS auth | © Digikare
20 |
27 |
42 |
43 |
44 |
45 |
46 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/server/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # nest-auth
2 |
3 | ## Installation
4 |
5 | ```bash
6 | $ npm install
7 | ```
8 |
9 |
10 | ## Before running
11 | Update the `config.ts` file add your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`
12 |
13 |
14 | ## Running the app
15 |
16 | ```bash
17 | # development
18 | $ npm run start
19 |
20 | # watch mode
21 | $ npm run start:dev
22 |
23 | # production mode
24 | npm run start:prod
25 | ```
26 |
27 | ## Test
28 |
29 | ```bash
30 | # unit tests
31 | $ npm run test
32 |
33 | # e2e tests
34 | $ npm run test:e2e
35 |
36 | # test coverage
37 | $ npm run test:cov
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src"
4 | ],
5 | "ext": "ts",
6 | "ignore": [
7 | "src/**/*.spec.ts"
8 | ],
9 | "exec": "ts-node -r tsconfig-paths/register src/main.ts"
10 | }
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nest-auth",
3 | "version": "1.0.0",
4 | "description": "description",
5 | "author": "",
6 | "license": "MIT",
7 | "scripts": {
8 | "format": "prettier --write \"**/*.ts\"",
9 | "start": "ts-node -r tsconfig-paths/register src/main.ts",
10 | "start:dev": "nodemon",
11 | "prestart:prod": "rm -rf dist && tsc",
12 | "start:prod": "node dist/main.js",
13 | "start:hmr": "node dist/server",
14 | "test": "jest",
15 | "test:cov": "jest --coverage",
16 | "test:e2e": "jest --config ./test/jest-e2e.json",
17 | "webpack": "webpack --config webpack.config.js"
18 | },
19 | "dependencies": {
20 | "@nestjs/common": "^5.0.0-beta.6",
21 | "@nestjs/core": "^5.0.0-beta.6",
22 | "@nestjs/microservices": "^5.3.11",
23 | "@nestjs/testing": "^5.0.0-beta.6",
24 | "@nestjs/websockets": "^5.0.0-beta.6",
25 | "express-session": "^1.15.6",
26 | "fastify-formbody": "^2.0.0",
27 | "passport": "^0.4.0",
28 | "passport-google-oauth": "^1.0.0",
29 | "reflect-metadata": "^0.1.12",
30 | "rxjs": "^6.0.0",
31 | "socket.io": "^2.1.1",
32 | "typescript": "^2.6.2"
33 | },
34 | "devDependencies": {
35 | "@types/express": "^4.0.39",
36 | "@types/jest": "^21.1.8",
37 | "@types/node": "^9.3.0",
38 | "@types/supertest": "^2.0.4",
39 | "jest": "^21.2.1",
40 | "nodemon": "^1.14.1",
41 | "prettier": "^1.11.1",
42 | "supertest": "^3.0.0",
43 | "ts-jest": "^21.2.4",
44 | "ts-loader": "^4.1.0",
45 | "ts-node": "^4.1.0",
46 | "tsconfig-paths": "^3.1.1",
47 | "tslint": "5.3.2",
48 | "webpack": "^4.2.0",
49 | "webpack-cli": "^2.0.13",
50 | "webpack-node-externals": "^1.6.0"
51 | },
52 | "jest": {
53 | "moduleFileExtensions": [
54 | "js",
55 | "json",
56 | "ts"
57 | ],
58 | "rootDir": "src",
59 | "testRegex": ".spec.ts$",
60 | "transform": {
61 | "^.+\\.(t|j)s$": "ts-jest"
62 | },
63 | "coverageDirectory": "../coverage"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/server/src/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import { AppController } from './app.controller';
4 | import { AppService } from './app.service';
5 |
6 | describe('AppController', () => {
7 | let app: TestingModule;
8 |
9 | beforeAll(async () => {
10 | app = await Test.createTestingModule({
11 | controllers: [AppController],
12 | providers: [AppService],
13 | }).compile();
14 | });
15 |
16 | describe('root', () => {
17 | it('should return "Hello World!"', () => {
18 | const appController = app.get(AppController);
19 | expect(appController.root()).toBe('Hello World!');
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/server/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Get, Controller } from '@nestjs/common';
2 | import { AppService } from './app.service';
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get()
9 | root(): string {
10 | return this.appService.root();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 | import { AuthModule } from './auth/auth.module';
5 |
6 | @Module({
7 | imports: [AuthModule],
8 | controllers: [AppController],
9 | providers: [ AppService ]
10 | })
11 | export class AppModule {}
12 |
--------------------------------------------------------------------------------
/server/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | root(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/server/src/auth/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller } from '@nestjs/common';
2 |
3 | @Controller()
4 | export class AuthController {}
5 |
--------------------------------------------------------------------------------
/server/src/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import {Module, NestModule} from '@nestjs/common';
2 | import {AuthController} from './auth.controller';
3 | import {GoogleStrategy} from './google.strategy';
4 | import {SessionAuthMiddleware} from './session-auth.middleware';
5 | import {StrategyCallbackMiddleware} from './strategy-callback.middleware';
6 | import {StrategyMiddleware} from './strategy.middleware';
7 | import {MiddlewaresConsumer} from '@nestjs/common/interfaces/middlewares';
8 |
9 | @Module({
10 | controllers: [AuthController],
11 | providers: [GoogleStrategy, SessionAuthMiddleware, StrategyCallbackMiddleware, StrategyMiddleware]
12 | })
13 | export class AuthModule implements NestModule {
14 |
15 | public configure(consumer : MiddlewaresConsumer) {
16 | consumer
17 | // Saving the socketId on session
18 | .apply(SessionAuthMiddleware)
19 | .forRoutes('/auth/google')
20 | // Authenticate to google signin api for /auth/google route
21 | .apply(StrategyMiddleware)
22 | .with ({provider: 'google'}) .forRoutes('/auth/google')
23 | // After signin google call this endpoint
24 | .apply(StrategyCallbackMiddleware).with ({provider: 'google'}) .forRoutes('auth/google/callback');
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/auth/google.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { OAuth2Strategy } from 'passport-google-oauth';
3 | import { Config } from '../config';
4 |
5 | import * as passport from 'passport';
6 |
7 | @Injectable()
8 | export class GoogleStrategy extends OAuth2Strategy {
9 | constructor() {
10 | // http://www.passportjs.org/docs/google/
11 | super({
12 | clientID: Config.GOOGLE_CLIENT_ID,
13 | clientSecret: Config.GOOGLE_CLIENT_SECRET,
14 | callbackURL: Config.GOOGLE_CB_URL,
15 | passReqToCallback: true,
16 | }, (req, accessToken, refreshToken, profile, done) => {
17 | const user: any = {
18 | email: profile.emails[0].value,
19 | photo: profile.photos[0].value.replace(/sz=50/gi, 'sz=250'),
20 | image: profile._json.image.url,
21 | displayName: profile.displayName,
22 | googleAccount: {
23 | googleId: profile.id,
24 | googleToken: accessToken,
25 | },
26 | };
27 | return done(null, user);
28 | });
29 | passport.use(this);
30 |
31 | passport.serializeUser((user, done) => {
32 | done(null, user);
33 | });
34 |
35 | passport.deserializeUser((user, done) => {
36 | done(null, user);
37 | });
38 |
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/server/src/auth/session-auth.middleware.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NestMiddleware } from '@nestjs/common';
2 | import { ExpressMiddleware } from '@nestjs/common/interfaces/middlewares/express-midleware.interface';
3 | import { NextFunction } from 'express';
4 |
5 | @Injectable()
6 | export class SessionAuthMiddleware implements NestMiddleware {
7 | constructor() {
8 | }
9 |
10 | async resolve(...args: any[]): Promise {
11 | return async (req: any, res: Response, next: NextFunction) => {
12 | if (req.query.socketId) {
13 | req.session.socketId = req.query.socketId;
14 | }
15 | next();
16 | };
17 |
18 | }
19 | }
--------------------------------------------------------------------------------
/server/src/auth/strategy-callback.middleware.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NestMiddleware } from '@nestjs/common';
2 | import { ExpressMiddleware } from '@nestjs/common/interfaces/middlewares/express-midleware.interface';
3 | import { NextFunction } from 'express';
4 |
5 | @Injectable()
6 | export class StrategyCallbackMiddleware implements NestMiddleware {
7 | constructor() {
8 | }
9 |
10 | async resolve(...args: any[]): Promise {
11 | return async (req: any, res: any, next: NextFunction) => {
12 | const io = req.app.get('io');
13 | const user = {
14 | name: req.user.displayName,
15 | photo: req.user.photo,
16 | };
17 | io.in(req.session.socketId).emit(args[0].provider, user);
18 | res.end();
19 | };
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/server/src/auth/strategy.middleware.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NestMiddleware } from '@nestjs/common';
2 | import { ExpressMiddleware } from '@nestjs/common/interfaces/middlewares/express-midleware.interface';
3 | import * as passport from 'passport';
4 |
5 | @Injectable()
6 | export class StrategyMiddleware implements NestMiddleware {
7 | constructor() {
8 | }
9 |
10 | async resolve(...args: any[]): Promise {
11 | if (args.length === 0 || !args[0].provider) {
12 | throw new Error('Missing provider for authenticate oauth');
13 | }
14 | const provider = args[0].provider;
15 | return passport.authenticate(provider, { scope: ['profile', 'email'] });
16 | }
17 | }
--------------------------------------------------------------------------------
/server/src/config.ts:
--------------------------------------------------------------------------------
1 | export class Config {
2 | // --- OAUTH --- Google
3 | static GOOGLE_CLIENT_ID : string = process.env.GOOGLE_CLIENT_ID || '550686690224-4oiimqcork5cb2r1jfk64ojooopj14vo.apps.googleusercontent.com';
4 | static GOOGLE_CLIENT_SECRET : string = process.env.GOOGLE_CLIENT_SECRET;
5 | static GOOGLE_CB_URL : string = process.env.GOOGLE_CB_URL || 'http://127.0.0.1:8080/auth/google/callback';
6 | }
--------------------------------------------------------------------------------
/server/src/main.hmr.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 |
4 | declare const module: any;
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(AppModule);
8 | await app.listen(3000);
9 |
10 | if (module.hot) {
11 | module.hot.accept();
12 | module.hot.dispose(() => app.close());
13 | }
14 | }
15 | bootstrap();
16 |
--------------------------------------------------------------------------------
/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 | import { WsAdapter } from '@nestjs/websockets';
4 | import * as socketio from 'socket.io';
5 | import * as expressSession from 'express-session';
6 | import * as passport from 'passport';
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(AppModule);
10 |
11 | // Add websocket on your server
12 | app.useWebSocketAdapter(new WsAdapter(app.getHttpServer()));
13 | // Connecting sockets to the server and adding them to the request
14 | // so that we can access them later in the controller
15 | const io = socketio(app.getHttpServer());
16 | app.set('io', io);
17 |
18 | app.use(passport.initialize());
19 | app.use(passport.session());
20 | app.use(expressSession({
21 | secret: 'TWC_2018',
22 | resave: true,
23 | saveUninitialized: true,
24 | }));
25 |
26 | await app.listen(8080);
27 |
28 | }
29 | bootstrap();
30 |
--------------------------------------------------------------------------------
/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import request from 'supertest';
2 | import { Test } from '@nestjs/testing';
3 | import { AppModule } from './../src/app.module';
4 | import { INestApplication } from '@nestjs/common';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeAll(async () => {
10 | const moduleFixture = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/GET /', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testRegex": ".e2e-spec.ts$",
5 | "transform": {
6 | "^.+\\.(t|j)s$": "ts-jest"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": false,
5 | "noImplicitAny": false,
6 | "removeComments": true,
7 | "noLib": false,
8 | "allowSyntheticDefaultImports": true,
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es6",
12 | "sourceMap": true,
13 | "allowJs": true,
14 | "outDir": "./dist",
15 | "baseUrl": "./src"
16 | },
17 | "include": [
18 | "src/**/*"
19 | ],
20 | "exclude": [
21 | "node_modules",
22 | "**/*.spec.ts"
23 | ]
24 | }
--------------------------------------------------------------------------------
/server/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {
7 | "no-unused-expression": true
8 | },
9 | "rules": {
10 | "eofline": false,
11 | "quotemark": [
12 | true,
13 | "single"
14 | ],
15 | "indent": false,
16 | "member-access": [
17 | false
18 | ],
19 | "ordered-imports": [
20 | false
21 | ],
22 | "max-line-length": [
23 | true,
24 | 150
25 | ],
26 | "member-ordering": [
27 | false
28 | ],
29 | "curly": false,
30 | "interface-name": [
31 | false
32 | ],
33 | "array-type": [
34 | false
35 | ],
36 | "no-empty-interface": false,
37 | "no-empty": false,
38 | "arrow-parens": false,
39 | "object-literal-sort-keys": false,
40 | "no-unused-expression": false,
41 | "max-classes-per-file": [
42 | false
43 | ],
44 | "variable-name": [
45 | false
46 | ],
47 | "one-line": [
48 | false
49 | ],
50 | "one-variable-per-declaration": [
51 | false
52 | ]
53 | },
54 | "rulesDirectory": []
55 | }
--------------------------------------------------------------------------------
/server/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const nodeExternals = require('webpack-node-externals');
4 |
5 | module.exports = {
6 | entry: ['webpack/hot/poll?1000', './src/main.hmr.ts'],
7 | watch: true,
8 | target: 'node',
9 | externals: [
10 | nodeExternals({
11 | whitelist: ['webpack/hot/poll?1000'],
12 | }),
13 | ],
14 | module: {
15 | rules: [
16 | {
17 | test: /\.tsx?$/,
18 | use: 'ts-loader',
19 | exclude: /node_modules/,
20 | },
21 | ],
22 | },
23 | mode: "development",
24 | resolve: {
25 | extensions: ['.tsx', '.ts', '.js'],
26 | },
27 | plugins: [
28 | new webpack.HotModuleReplacementPlugin(),
29 | ],
30 | output: {
31 | path: path.join(__dirname, 'dist'),
32 | filename: 'server.js',
33 | },
34 | };
35 |
--------------------------------------------------------------------------------