Skip to content

Commit db5a5da

Browse files
committed
release: 1.2.0-rc.1
1 parent 094654a commit db5a5da

19 files changed

+2616
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Changelog and release notes
22

3-
## Unreleased
3+
<!-- ## Unreleased -->
44
<!-- here goes all the unreleased changes descriptions -->
5+
6+
## v1.2.0-rc.1
57
### Features
68
- **Breaking Change**: `AuthChecker` type is now "function or class" - update to `AuthCheckerFn` if the function form is needed in the code
79
- support class-based auth checker, which allows for dependency injection

website/i18n/en.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,56 @@
502502
"version-1.1.1/version-1.1.1-validation": {
503503
"title": "Argument and Input validation",
504504
"sidebar_label": "Validation"
505+
},
506+
"version-1.2.0-rc.1/version-1.2.0-rc.1-authorization": {
507+
"title": "Authorization"
508+
},
509+
"version-1.2.0-rc.1/version-1.2.0-rc.1-bootstrap": {
510+
"title": "Bootstrapping"
511+
},
512+
"version-1.2.0-rc.1/version-1.2.0-rc.1-complexity": {
513+
"title": "Query complexity"
514+
},
515+
"version-1.2.0-rc.1/version-1.2.0-rc.1-custom-decorators": {
516+
"title": "Custom decorators"
517+
},
518+
"version-1.2.0-rc.1/version-1.2.0-rc.1-dependency-injection": {
519+
"title": "Dependency injection"
520+
},
521+
"version-1.2.0-rc.1/version-1.2.0-rc.1-directives": {
522+
"title": "Directives"
523+
},
524+
"version-1.2.0-rc.1/version-1.2.0-rc.1-examples": {
525+
"title": "Examples",
526+
"sidebar_label": "List of examples"
527+
},
528+
"version-1.2.0-rc.1/version-1.2.0-rc.1-extensions": {
529+
"title": "Extensions"
530+
},
531+
"version-1.2.0-rc.1/version-1.2.0-rc.1-generic-types": {
532+
"title": "Generic Types"
533+
},
534+
"version-1.2.0-rc.1/version-1.2.0-rc.1-interfaces": {
535+
"title": "Interfaces"
536+
},
537+
"version-1.2.0-rc.1/version-1.2.0-rc.1-middlewares": {
538+
"title": "Middleware and guards"
539+
},
540+
"version-1.2.0-rc.1/version-1.2.0-rc.1-performance": {
541+
"title": "Performance"
542+
},
543+
"version-1.2.0-rc.1/version-1.2.0-rc.1-resolvers": {
544+
"title": "Resolvers"
545+
},
546+
"version-1.2.0-rc.1/version-1.2.0-rc.1-subscriptions": {
547+
"title": "Subscriptions"
548+
},
549+
"version-1.2.0-rc.1/version-1.2.0-rc.1-unions": {
550+
"title": "Unions"
551+
},
552+
"version-1.2.0-rc.1/version-1.2.0-rc.1-validation": {
553+
"title": "Argument and Input validation",
554+
"sidebar_label": "Validation"
505555
}
506556
},
507557
"links": {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
title: Authorization
3+
id: version-1.2.0-rc.1-authorization
4+
original_id: authorization
5+
---
6+
7+
Authorization is a core feature used in almost all APIs. Sometimes we want to restrict data access or actions for a specific group of users.
8+
9+
In express.js (and other Node.js frameworks) we use middleware for this, like `passport.js` or the custom ones. However, in GraphQL's resolver architecture we don't have middleware so we have to imperatively call the auth checking function and manually pass context data to each resolver, which might be a bit tedious.
10+
11+
That's why authorization is a first-class feature in `TypeGraphQL`!
12+
13+
## How to use
14+
15+
First, we need to use the `@Authorized` decorator as a guard on a field, query or mutation.
16+
Example object type field guards:
17+
18+
```typescript
19+
@ObjectType()
20+
class MyObject {
21+
@Field()
22+
publicField: string;
23+
24+
@Authorized()
25+
@Field()
26+
authorizedField: string;
27+
28+
@Authorized("ADMIN")
29+
@Field()
30+
adminField: string;
31+
32+
@Authorized(["ADMIN", "MODERATOR"])
33+
@Field({ nullable: true })
34+
hiddenField?: string;
35+
}
36+
```
37+
38+
We can leave the `@Authorized` decorator brackets empty or we can specify the role/roles that the user needs to possess in order to get access to the field, query or mutation.
39+
By default the roles are of type `string` but they can easily be changed as the decorator is generic - `@Authorized<number>(1, 7, 22)`.
40+
41+
Thus, authorized users (regardless of their roles) can only read the `publicField` or the `authorizedField` from the `MyObject` object. They will receive `null` when accessing the `hiddenField` field and will receive an error (that will propagate through the whole query tree looking for a nullable field) for the `adminField` when they don't satisfy the role constraints.
42+
43+
Sample query and mutation guards:
44+
45+
```typescript
46+
@Resolver()
47+
class MyResolver {
48+
@Query()
49+
publicQuery(): MyObject {
50+
return {
51+
publicField: "Some public data",
52+
authorizedField: "Data for logged users only",
53+
adminField: "Top secret info for admin",
54+
};
55+
}
56+
57+
@Authorized()
58+
@Query()
59+
authedQuery(): string {
60+
return "Authorized users only!";
61+
}
62+
63+
@Authorized("ADMIN", "MODERATOR")
64+
@Mutation()
65+
adminMutation(): string {
66+
return "You are an admin/moderator, you can safely drop the database ;)";
67+
}
68+
}
69+
```
70+
71+
Authorized users (regardless of their roles) will be able to read data from the `publicQuery` and the `authedQuery` queries, but will receive an error when trying to perform the `adminMutation` when their roles don't include `ADMIN` or `MODERATOR`.
72+
73+
Next, we need to create our auth checker function. Its implementation may depend on our business logic:
74+
75+
```typescript
76+
export const customAuthChecker: AuthChecker<ContextType> = (
77+
{ root, args, context, info },
78+
roles,
79+
) => {
80+
// here we can read the user from context
81+
// and check his permission in the db against the `roles` argument
82+
// that comes from the `@Authorized` decorator, eg. ["ADMIN", "MODERATOR"]
83+
84+
return true; // or false if access is denied
85+
};
86+
```
87+
88+
The second argument of the `AuthChecker` generic type is `RoleType` - used together with the `@Authorized` decorator generic type.
89+
90+
Auth checker can be also defined as a class - this way we can leverage the dependency injection mechanism:
91+
92+
```ts
93+
export class CustomAuthChecker implements AuthCheckerInterface<ContextType> {
94+
// inject dependency
95+
constructor(private readonly userRepository: Repository<User>) {}
96+
97+
check({ root, args, context, info }: ResolverData<ContextType>, roles: string[]) {
98+
const userId = getUserIdFromToken(context.token);
99+
// use injected service
100+
const user = this.userRepository.getById(userId);
101+
102+
// custom logic here, e.g.:
103+
return user % 2 === 0;
104+
}
105+
}
106+
```
107+
108+
The last step is to register the function or class while building the schema:
109+
110+
```typescript
111+
import { customAuthChecker } from "../auth/custom-auth-checker.ts";
112+
113+
const schema = await buildSchema({
114+
resolvers: [MyResolver],
115+
// here we register the auth checking function
116+
// or defining it inline
117+
authChecker: customAuthChecker,
118+
});
119+
```
120+
121+
And it's done! 😉
122+
123+
If we need silent auth guards and don't want to return authorization errors to users, we can set the `authMode` property of the `buildSchema` config object to `"null"`:
124+
125+
```typescript
126+
const schema = await buildSchema({
127+
resolvers: ["./**/*.resolver.ts"],
128+
authChecker: customAuthChecker,
129+
authMode: "null",
130+
});
131+
```
132+
133+
It will then return `null` instead of throwing an authorization error.
134+
135+
## Recipes
136+
137+
We can also use `TypeGraphQL` with JWT authentication.
138+
Here's an example using `apollo-server-express`:
139+
140+
```typescript
141+
import express from "express";
142+
import { ApolloServer, gql } from "apollo-server-express";
143+
import * as jwt from "express-jwt";
144+
145+
import { schema } from "../example/above";
146+
147+
const app = express();
148+
const path = "/graphql";
149+
150+
// Create a GraphQL server
151+
const server = new ApolloServer({
152+
schema,
153+
context: ({ req }) => {
154+
const context = {
155+
req,
156+
user: req.user, // `req.user` comes from `express-jwt`
157+
};
158+
return context;
159+
},
160+
});
161+
162+
// Mount a jwt or other authentication middleware that is run before the GraphQL execution
163+
app.use(
164+
path,
165+
jwt({
166+
secret: "TypeGraphQL",
167+
credentialsRequired: false,
168+
}),
169+
);
170+
171+
// Apply the GraphQL server middleware
172+
server.applyMiddleware({ app, path });
173+
174+
// Launch the express server
175+
app.listen({ port: 4000 }, () =>
176+
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
177+
);
178+
```
179+
180+
Then we can use standard, token based authorization in the HTTP header like in classic REST APIs and take advantage of the `TypeGraphQL` authorization mechanism.
181+
182+
## Example
183+
184+
See how this works in the [simple real life example](https://github.com/MichalLytek/type-graphql/tree/master/examples/authorization).

0 commit comments

Comments
 (0)