· 6 years ago · Oct 25, 2019, 04:30 AM
1/*
2* Typical Auth in REST API
3*/
4const authMiddleware = (req, res, next) => {
5 if(userIsAuthenticated(req)){
6 next();
7 }else{
8 res.status(401).send('Sorry, you\'re not allowed here!' );
9 }
10}
11
12app.get('/private-data', authMiddleware, (req, res, next) => {
13 res.send(somePrivateData);
14})
15
16
17/*
18* auth in graphQL
19*/
20// first, verify authentication
21const jwt = require('express-jwt');
22const jwtDecode = require('jwt-decode');
23
24const jwtMiddleware = jwt({secret: 'some-strong-secret-key'});
25
26const getUserFromJwt = (req, res, next) => {
27 const authHeader = req.headers.authorization;
28 req.user = jwtDecode(authHeader);
29 next();
30}
31
32app.use(jwtMiddleware);
33app.use(getUserFromJwt);
34
35
36// now we can use the payload in our resolvers
37const resolvers = {
38 Query: {
39 articlesByAuthor: (root, args, {req, res, next}, info) => {
40 return model.getArticles(req.user.sub);
41 }
42 }
43}
44
45//do authorization checks
46const resolvers = {
47 Query: {
48 articlesByAuthor: (root, args, {req, res, next}, info) => {
49 const scope = req.user.scope;
50 if(scope.includes('read:articles')){
51 return model.getArticles(req.user.id);
52 }
53 }
54 }
55}
56
57// optimize upper code as:
58const resolvers = {
59 Query: {
60 articlesByAuthor: (root, args, {req, res, next}, info) => {
61 return checkScopeAndResolve(
62 req.user.scope,
63 ['read:articles'],
64 controller
65 );
66 }
67 }
68}
69
70const checkScopeAndResolve = (scope, expectedScope, controller) => {
71 const hasScope = scope.includes('expectedScope');
72 if(hasScope){
73 return controller.apply(this);
74 }
75 return new AuthorizationError();
76}
77
78const controller = model.getArticles(req.user.id);
79
80
81// check the JWT in the wrapper
82import { createError } from 'apollo-errors';
83import jwt from 'jsonwebtoken';
84
85const AuthorizationError = createError('AuthorizationError', {
86 message: 'You are not authorized!'
87});
88
89const checkScopeAndResolve = (context, expectedScope, controller) => {
90 const token = context.headers.authorization;
91 try{
92 const jwtPayload = jwt.verify(token.replace('Bearer ', ''), secretKey);
93 const hasScope = jwtPayload.scope.includes(expectedScope);
94 if(hasScope){
95 return controller.apply(this);
96 }
97
98 }catch(err){
99 throw new AuthorizationError();
100 }
101
102}
103
104// possibility for per-field authorization checks
105// resolver-level authentication checks
106// what if we want to limit access to specific fields?
107// use custom directives on server
108const typeDefs = `
109 directive @isAuthenticated on QUERY | FIELD
110 directive @hasScope(scope: [String]) on QUERY | FIELD
111
112 type Article {
113 id: ID!
114 author: String!
115 reviewerComments: [ReviewerComment] @hasScope(scope: ["read:comments"])
116
117 }
118
119 type Query {
120 allArticles: [Article] @isAuthenticated
121 }
122`;