Application - Server - Authenticaion
# Authentication
Authentication sử dụng jwt-token
# Config
JWTService:
import { JwtService } from '@nestjs/jwt'; import { jwtCredentials } from '../config'; export const jwtService = new JwtService({ signOptions: { algorithm: 'RS256', }, ...jwtCredentials, });
1
2
3
4
5
6
7
8
9Credential:
import * as Path from 'path'; import * as FS from 'fs-extra'; export const jwtCredentials = { privateKey: FS.readFileSync( Path.resolve(__dirname, '..', '..', '..', 'etc', 'cert', 'private.key'), 'utf8', ), publicKey: FS.readFileSync( Path.resolve(__dirname, '..', '..', '..', 'etc', 'cert', 'public.key'), 'utf8', ), };
1
2
3
4
5
6
7
8
9
10
11
12
13
# Login:
GraphQL Schema: UserGraphql
Định nghĩa input, response và mutation login. Thực hiện login bằng email và password. Sau khi login thành công sẽ tạo Access Token, Refresh Token Và publish data đến subscription notify. Các client đang lắng nge subscription này sẽ nhận được message "{user name} logged".
### INPUT ### input LoginUserInput { email: String! password: String! } ### TYPES ### type User { _id: String! name: String! email: String! userType: Int createdAt: String! updatedAt: String! roles: [Role] } type LoginResponse { token: String refreshToken: String user: User } ### MUTATION ### type Mutation { login(input: LoginUserInput!): LoginResponse }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Resolver: UserResolver
@Mutation(() => LoginResponseUserDto) async login(@Args('input') input: LoginUserDto) { return await this.authService.login(input); }
1
2
3
4AuthService:
Refresh Token được lưu vào db, dùng để tạo mới access token khi token này hết hạn. Xem chi tiết xử lí ở TokenService
async login(input: LoginUserDto): Promise<LoginResponseUserDto> { try { const {email, password} = input; const user = await this.authRepository.findOne({email}); if (!user || !(await user.matchesPassword(password))) { return null; } /* Create Access Token */ const token = await this.tokenService.createAccessToken({ id: user._id, }); /* Create Refresh Token */ const refreshToken = await this.tokenService.createRefreshToken({ userId: user._id, }); return {token, refreshToken, user}; } catch (error) { logger.error(error.toString(), {}); throw new AuthenticationError(error.toString()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Verify JWT Token:
# Query & mutation:
Handle context của request tại GraphqlService: Get token từ headers, verify token và merge thông tin user vào context.
context: async ({req, res, connection}) => {
if (connection) {
return {
req: connection.context,
};
}
let currentUser: any;
const {token} = req.headers;
const excludeOperations = ['loginQuery', 'refreshTokenQuery'];
if (req.body && !excludeOperations.includes(req.body.operationName) && token) {
currentUser = await this.userService.findByToken(token);
}
return {
req,
res,
currentUser,
};
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Subscription:
Xử lí tương tự nhưng token được get từ connectionParams
của WebSocketLink
Apollo client.
subscriptions: {
onConnect: async (connectionParams: any) => {
const authToken = connectionParams.authToken;
// extract user information from token
const currentUser = await this.userService.findByToken(authToken);
// return user info to add them to the context later
return {currentUser};
},
},
2
3
4
5
6
7
8
9
# GraphQl Schema directive:
Dùng directive để tạo middleware cho các operator. VD:
users: [User] @hasRole(role: "admin")
user(_id: String!): User @isAuthenticated
2
AuthenticateDirective: Sau khi handle context chỉ cần kiểm tra thông tin user.
import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; import { defaultFieldResolver, GraphQLField } from 'graphql'; export class AuthenticateDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field: GraphQLField<any, any>) { const {resolve = defaultFieldResolver} = field; field.resolve = function(...args) { const { currentUser } = args[2]; if (!currentUser) { throw new AuthenticationError('Authentication token is invalid.'); } return resolve.apply(this, args); }; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18RoleDirective: Có thêm parameter role.
export class RoleDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field: GraphQLField<any, any>) { const {resolve = defaultFieldResolver} = field; const { role } = this.args; field.resolve = function(...args) { const {currentUser} = args[2]; if (!currentUser) { throw new AuthenticationError('Authentication token is invalid.'); } if (!currentUser.roles) { throw new AuthenticationError('Your Account has No Role.'); } const userRoles = currentUser.roles.map(r => r.code); if (!userRoles.includes(role)) { return new AuthenticationError('Your Account has No Role.'); } return resolve.apply(this, args); }; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Series:
- Preview (opens new window)
- Server - NestJs & GraphQL (opens new window)
- Server - Module (opens new window)
- Server - Validation (opens new window)
- Server - Authentication (opens new window)
- Server - Supscription & Upload file (opens new window)
- Client - Angular & GraphQL (opens new window)
GitHub: https://github.com/ninhnguyen22/nestjs-angular-graphql (opens new window)