@notiz/nest-auth

Nest Auth

Usage no npm install needed!

<script type="module">
  import notizNestAuth from 'https://cdn.skypack.dev/@notiz/nest-auth';
</script>

README

Nest Auth

npm version

Installation

npm i @notiz/nest-auth

nest add nestjs-prisma

# auth
npm i @nestjs/config @nestjs/jwt @nestjs/passport passport passport-jwt
# auth types
npm i -D @types/passport-jwt

# oauth
npm i openid-client @nestjs/axios

# utils
npm i bcrypt dayjs cuid

Rest with Swagger

npm install --save @nestjs/swagger swagger-ui-express class-transformer class-validator

GraphQL

npm i @nestjs/graphql graphql@^15 apollo-server-express graphql-scalars

Usage

Prisma Schema

Nest Auth requires following models and enums in your schema.prisma. You can add relations and more properties to the models as you like.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name          String?
  email         String    @unique
  emailVerified DateTime?
  password      String?
  role          Role      @default(USER)
  image         String?

  providerAccountId String?
  authProvider      AuthProvider @default(EMAIL)
  accessToken       String?
  refreshToken      String?

  verificationRequest VerificationRequest[]

  // TODO add relations and more properties

  @@unique(fields: [authProvider, providerAccountId], name: "socialAuth")
}

enum Role {
  ADMIN
  USER
}

enum AuthProvider {
  EMAIL
  GOOGLE
  GITHUB
}

model VerificationRequest {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  type    VerificationType
  token   String
  expires DateTime

  user   User?   @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  userId String?

  @@unique(fields: [type, token], name: "verification")
}

enum VerificationType {
  ACCOUNT
  PASSWORDLESS
  RESET_PASSWORD
}

DB Diagram

Authentication

Rest

import {
  LoggerMailService,
  AuthModule,
  PasswordlessModule,
} from '@notiz/nest-auth';
import { PrismaModule } from 'nestjs-prisma';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { UsersModule } from './users/users.module';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    PrismaModule.forRoot({ isGlobal: true }),
    AuthModule.forRoot(LoggerMailService),
    PasswordlessModule.forRoot(LoggerMailService),
  ],
  controllers: [],
  providers: [LoggerMailService],
})
export class AppModule {}

GraphQL

import {
  LoggerMailService,
  GraphqlAuthModule,
  GraphqlPasswordlessModule,
} from '@notiz/nest-auth';
import { PrismaModule } from 'nestjs-prisma';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { UsersModule } from './users/users.module';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    PrismaModule.forRoot({ isGlobal: true }),

    // GraphQL
    GraphQLModule.forRoot({
      playground: true,
      autoSchemaFile: join(process.cwd(), 'demo/schema.gql'),
      sortSchema: true,
    }),
    GraphqlAuthModule.forRoot(LoggerMailService),
    GraphqlPasswordlessModule.forRoot(LoggerMailService),
  ],
  controllers: [],
  providers: [LoggerMailService],
})
export class AppModule {}

OAuth

Install the following packages to support OAuth

npm i openid-client @nestjs/axios

Import SocialLoginModule for social login endpoints.

import { LoggerMailService, SocialLoginModule, Google, GitHub } from '@notiz/nest-auth';
import { PrismaModule } from 'nestjs-prisma';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { UsersModule } from './users/users.module';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    PrismaModule.forRoot({ isGlobal: true }),
    SocialLoginModule.forRoot(LoggerMailService,
      providers: [
        // enable your oauth providers
        GitHub({
          clientId: process.env.GITHUB_CLIENT_ID,
          clientSecret: process.env.GITHUB_CLIENT_SECRET,
        }),
        Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        }),
      ]
    ),
    UsersModule,
  ],
  controllers: [],
  providers: [LoggerMailService],
})
export class AppModule {}

Note: Checkout supported OAuth Providers

See .env variables necessary for OAuth providers.

Securing endpoints

Guards

To secure your own endpoints use one of the following guards.

Guard API Requires Description
JwtGuard Rest, GraphQL - valid JWT access token
RolesGuard Rest, GraphQL @Roles(roles) JwtGuard + the user must have a specific role
AuthGuard Rest - Combined decorator with @Roles(role), RolesGuard, @ApiBearerAuth, @ApiUnauthorizedResponse and @ApiForbiddenResponse
GqlAuthGuard GraphQL - Combined decorator with @Roles(role) and RolesGuard

Guards can be used as controller/resolver-scoped or method-scoped.

Controller/Resolver scoped

Controller

@Controller('products')
@ApiTags('products')
@UseGuards(JwtGuard)
export class ProductsController {

  @Get()
  ...

  @Post()
  ...
}

@Controller('admin')
@ApiTags('admin')
@AuthGuard('ADMIN')
export class AdminController {

  @Get()
  ...

  @Post()
  ...
}

@Controller('products')
@ApiTags('products')
@AuthGuard('USER')
export class ProductsController {

  @Get()
  ...

  @Post()
  ...
}

Resolver

@Resolver(() => Product)
@UseGuards(JwtGuard)
export class ProductsResolver {

  @Mutation(() => Product)
  ...

  @Query(() => [Product], { name: 'products' })
  ...
}

@Resolver(() => Admin)
@GqlAuthGuard('ADMIN')
export class AdminResolver {

  @Mutation(() => Product)
  ...

  @Query(() => [Product], { name: 'products' })
  ...
}

@Resolver(() => Product)
@GqlAuthGuard('USER')
export class ProductsResolver {

  @Mutation(() => Product)
  ...

  @Query(() => [Product], { name: 'products' })
  ...
}
Method scoped

Controller

@Controller('users')
@ApiTags('users')
export class UsersController {

  @Get('me')
  @UseGuards(JwtGuard)
  @ApiResponse({ status: 200, type: UserEntity })
  me(@CurrentUser() user: User): UserEntity {
    return new UserEntity(user);
  }

  @Get('me')
  @AuthGuard('ADMIN')
  @ApiResponse({ status: 200, type: [UserEntity] })
  findAllUsers(): UserEntity[] {
    return [...];
  }
}

Resolver

@Resolver(() => User)
export class UsersResolver {
  @Query(() => User)
  @UseGuards(JwtGuard)
  async me(@CurrentUser() user: User): Promise<User> {
    return user;
  }

  @Query(() => User)
  @GqlAuthGuard('ADMIN')
  async findAllUsers(): Promise<User[]> {
    return [...];
  }
}

Current User

To access the current user in your endpoint use @CurrentUser() decorator. This works with Rest and GraphQL.

  1. Receive full user object
import { CurrentUser, AuthGuard, GqlAuthGuard } from '@notiz/nest-auth';

// REST
@Get('me')
@AuthGuard()
@ApiResponse({ status: 200, type: UserEntity })
me(@CurrentUser() user: User): UserEntity {
  return new UserEntity(user);
}

// GraphQL
@Query(() => User)
@UseGuards(GqlAuthGuard)
me(@CurrentUser() user: User): Promise<User> {
  return user;
}
  1. Access user properties
import { CurrentUser, AuthGuard, GqlAuthGuard } from '@notiz/nest-auth';

// REST
@Get('updateProfile')
@AuthGuard()
@ApiResponse({ status: 204 })
updateProfile(@CurrentUser('id') id: string) {
  console.log(id);
}

// GraphQL
@Query(() => Void)
@UseGuards(GqlAuthGuard)
updateProfile(@CurrentUser('email') email: string) {
   console.log(email);
}

.env

# JWT
JWT_ACCESS_TOKEN_SECRET=accessTokenSecret
JWT_ACCESS_TOKEN_EXPIRES_IN=7d
JWT_REFRESH_TOKEN_SECRET=refreshTokenSecret
JWT_REFRESH_TOKEN_EXPIRES_IN=30d

# oauth
BASE_URL=http://localhost:3000
AUTH_SUCCESS_URL=/auth/login
AUTH_ERROR_URL=/auth/login

# Bycrpt
BCRYPT_ROUNDS=10

# Google
GOOGLE_CLIENT_ID=google_id
GOOGLE_CLIENT_SECRET=google_secret

# GitHub
GITHUB_CLIENT_ID=github_id
GITHUB_CLIENT_SECRET=github_secret