|
| 1 | +--- |
| 2 | +title: Authorization |
| 3 | +id: version-0.17.0-authorization |
| 4 | +original_id: authorization |
| 5 | +--- |
| 6 | + |
| 7 | +Authorization is a core feature used in almost all APIs. Sometimes we want to restrict access to some actions or reading some data only for specific group of users. |
| 8 | + |
| 9 | +In express.js (and other Node.js framework) we use middlewares for this, like `passport.js` or the custom ones. However in GraphQL's resolvers architecture we don't have middlewares so we have to imperatively call the auth checking function and manually passing context data in each resolver, which might be quite tedious work. |
| 10 | + |
| 11 | +And that's why authorization is a first-class feature in `TypeGraphQL`! |
| 12 | + |
| 13 | +## How to use? |
| 14 | + |
| 15 | +At first, you need to use `@Authorized` decorator as a guard on a field or a query/mutation. |
| 16 | +Example object type's fields 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 | +You can leave the `@Authorized` decorator brackets empty or you can specify the roles that the user needs to have to get access to the field, query or mutation. |
| 39 | +By default the roles are `string` but you can change it easily as the decorator is generic - `@Authorized<number>(1, 7, 22)`. |
| 40 | + |
| 41 | +This way authed users (regardless of theirs roles) could read only `publicField` or `authorizedField` from `MyObject` object. They will receive `null` when accessing `hiddenField` field and will receive error (that will propagate through the whole query tree looking for nullable field) for `adminField` when they don't satisfy roles constraints. |
| 42 | + |
| 43 | +Sample query and mutations guards: |
| 44 | + |
| 45 | +```typescript |
| 46 | +@Resolver() |
| 47 | +class MyResolver { |
| 48 | + @Query() |
| 49 | + publicQuery(): MyObject { |
| 50 | + return { |
| 51 | + publicField: "Some public data", |
| 52 | + authorizedField: "Data only for logged users", |
| 53 | + adminField: "Top secret info for admin", |
| 54 | + }; |
| 55 | + } |
| 56 | + |
| 57 | + @Authorized() |
| 58 | + @Query() |
| 59 | + authedQuery(): string { |
| 60 | + return "Only for authed users!"; |
| 61 | + } |
| 62 | + |
| 63 | + @Authorized("ADMIN", "MODERATOR") |
| 64 | + @Mutation() |
| 65 | + adminMutation(): string { |
| 66 | + return "You are an admin/moderator, you can safely drop database ;)"; |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +Authed users (regardless of theirs roles) will be able to read data from `publicQuery` and `authedQuery` but will receive error trying to perform `adminMutation` when their roles doesn't include `ADMIN` or `MODERATOR`. |
| 72 | + |
| 73 | +In next step, you need to create your auth checker function. Its implementation may depends on your business logic: |
| 74 | + |
| 75 | +```typescript |
| 76 | +export const customAuthChecker: AuthChecker<ContextType> = ( |
| 77 | + { root, args, context, info }, |
| 78 | + roles, |
| 79 | +) => { |
| 80 | + // here you can read user from context |
| 81 | + // and check his permission in db against `roles` argument |
| 82 | + // that comes from `@Authorized`, eg. ["ADMIN", "MODERATOR"] |
| 83 | + |
| 84 | + return true; // or false if access denied |
| 85 | +}; |
| 86 | +``` |
| 87 | + |
| 88 | +The second argument of `AuthChecker` generic type is `RoleType` - use it together with `@Authorized` decorator generic type. |
| 89 | + |
| 90 | +The last step is to register the function while building the schema: |
| 91 | + |
| 92 | +```typescript |
| 93 | +import { customAuthChecker } from "../auth/custom-auth-checker.ts"; |
| 94 | + |
| 95 | +const schema = await buildSchema({ |
| 96 | + resolvers: [MyResolver], |
| 97 | + // here we register the auth checking function |
| 98 | + // or defining it inline |
| 99 | + authChecker: customAuthChecker, |
| 100 | +}); |
| 101 | +``` |
| 102 | + |
| 103 | +And it's done! 😉 |
| 104 | + |
| 105 | +If you need silent auth guards and you don't want to return auth errors to users, you can set `authMode` property of `buildSchema` config object to `"null"`: |
| 106 | + |
| 107 | +```typescript |
| 108 | +const schema = await buildSchema({ |
| 109 | + resolvers: ["./**/*.resolver.ts"], |
| 110 | + authChecker: customAuthChecker, |
| 111 | + authMode: "null", |
| 112 | +}); |
| 113 | +``` |
| 114 | + |
| 115 | +It will then return `null` instead of throwing authorization error. |
| 116 | + |
| 117 | +## Recipes |
| 118 | + |
| 119 | +You can also use `TypeGraphQL` with JWT authentication. Example using `apollo-server-express`: |
| 120 | + |
| 121 | +```typescript |
| 122 | +import express from "express"; |
| 123 | +import { ApolloServer, gql } from "apollo-server-express"; |
| 124 | +import * as jwt from "express-jwt"; |
| 125 | + |
| 126 | +import { schema } from "../example/above"; |
| 127 | + |
| 128 | +const app = express(); |
| 129 | +const path = "/graphql"; |
| 130 | + |
| 131 | +// Create a GraphQL server |
| 132 | +const server = new ApolloServer({ |
| 133 | + schema, |
| 134 | + context: ({ req }) => { |
| 135 | + const context = { |
| 136 | + req, |
| 137 | + user: req.user, // `req.user` comes from `express-jwt` |
| 138 | + }; |
| 139 | + return context; |
| 140 | + }, |
| 141 | +}); |
| 142 | + |
| 143 | +// Mount a jwt or other authentication middleware that is run before the GraphQL execution |
| 144 | +app.use( |
| 145 | + path, |
| 146 | + jwt({ |
| 147 | + secret: "TypeGraphQL", |
| 148 | + credentialsRequired: false, |
| 149 | + }), |
| 150 | +); |
| 151 | + |
| 152 | +// Apply the GraphQL server middleware |
| 153 | +server.applyMiddleware({ app, path }); |
| 154 | + |
| 155 | +// Launch the express server |
| 156 | +app.listen({ port: 4000 }, () => |
| 157 | + console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`), |
| 158 | +); |
| 159 | +``` |
| 160 | + |
| 161 | +Then you can use standard, token based authorization in HTTP header like in classic REST API and take advantages of `TypeGraphQL` authorization mechanism. |
| 162 | + |
| 163 | +## Example |
| 164 | + |
| 165 | +You can see how this works together in the [simple real life example](https://github.com/19majkel94/type-graphql/tree/master/examples/authorization). |
0 commit comments