The following section assumes you are familiar with authentication in
nestjs.
nestjs-query provides authorization helpers out of the box to reduce the amount of boilerplate typically required.
The nestjs-query graphql package exposes decorators and options to allow the following
- Additional filtering for objects based on the graphql context.
- Filtering relations based on the graphql context.
- Low level authorization service support when your authorizer needs to use other services or additional information that is not in the graphql context.
If you are looking to modify incoming requests based on the context, take a look at the hooks documentation
Authorization is invoked as the last step before calling the
QueryService.Getting Started
All examples assume you have a guard that adds auser to the req on the context.
JWTAuthGuard described in implementing passport jwt nestjs docs.
To enable the guard on your resolver endpoints you use the guards option when setting up your resolver.
The guards option will ensure that all queries and mutations will have the guard added so the user is added to the request.
todo-item/todo-item.module.ts
@Authorize Decorator
The@ptc-org/nestjs-query-graphql package includes an @Authorize decorator that allows you to add additional filter criteria to authorize an incoming request.
The @Authorize decorator accepts the following types.
- An
objectthat has anauthorizemethod that returns a Filter for the DTO. - An instance of an
Authorizerthat implements theauthorizeandauthorizeRelationmethods. - An
Authorizerclass reference that implements theAuthorizerinterface. TheAuthorizerclass will be instantiated using thenestjs’s dependency injection.
@Authorize decorator does not return an unauthorized error instead the following will occur:
queryManyresults will not include any DTOs that do not match the filter criteria.findOnewill return a not found for a DTO that cannot be found for theidand auth filter.updateOnewill return a not found error if the DTO to update cannot be found for theidand auth filter.updateManywill exclude any records that do not match the user provided filter and the auth filter from being updated.deleteOnewill return a not found error if the DTO to delete cannot be found for theidand auth filter.deleteManywill exclude any records that do not match the user provided filter and the auth filter from being deleted.
You can throw an
UnauthorizedException or return a rejected promise with an UnauthorizedException in your authorize
function, if you can determine at that point that the user should not be able to access the endpoint.authorize function returns a Filter that includes the ownerId to ensure that only TodoItems that belong to the authenticated user are returned.
todo-item/dto/todo-item.dto.ts
The above example is pretty straight forward, however your authorize function can be as complex as you need it to be based on
information in the context.
Relation Filtering
By default when relations are queried any additional filters defined using the@Authorize decorator on the relation DTO will also be included.
When mutating relations
- If the DTO that is having a relation(s) added or removed cannot be found for the
idand auth filter a not found error will be returned. - When adding or removing a single relation if the relation cannot be found for the
idand auth filter a not found error will be returned. - When adding or removing multiple relations if all relations cannot be found a not found error will be throw.
SubTaskDTO definition whenever the subTasks connection is queried through a todoItem, only subTasks that belong to the user will be returned.
sub-task/dto/sub-task.dto.ts
Customizing Relation Authorization
If you run into a case where you need to handle authorization for a relation differently from the@Authorize decorator on the relation DTO you can specify the auth option in your relation/connection decorator.
For example you could define the subtasks with the auth option, only allowing completed subtasks to be returned.
Custom Authorizer
When you need more control over authorization you can create aCustomAuthorizer. You may want to use a CustomAuthorizer if you need to use additional services to do authorization for a DTO.
The CustomAuthorizer interface ensures two methods:
authorize- Should return a filter that should be used for all queries and mutations for the DTO.authorizeRelation- Optionally modify the filter for the relation that will be used when querying the relation or adding/removing relations to/from the DTO. If undefined is returned, the authorization filter of the relation DTO will be used instead.
SubTasks. You can use this as a base to create a more complex authorizers that depends on other services.
sub-task/sub-task.authorizer.ts
SubTaskAuthorizer you only need to provide it as an argument to the @Authorize decorator
sub-task/sub-task.dto.ts
Using Authorizers In Your Resolver
The easiest way to leverageAuthorizers in a custom resolver is to use the AuthorizerInterceptor and AuthorizerFilter param decorator.
In this example there are two important additions:
- The
AuthorizerInterceptoris added to theTodoItemResolveras an interceptor, this interceptor will add the authorizer to the context so it can be used down stream - The
AuthorizerFilterparam decorator uses the authorizer added by the interceptor to create an authorizer filter.
@InjectAuthorizer Decorator
If you need access to an authorizer for a DTO you can use the@InjectAuthorizer decorator.
The most common use case for using the @InjectAuthorizer decorator is when you are not using the autogenerated resolvers provided by nestjs-query.
In this example the Authorizer is injected as a readonly property you can then use it for any custom methods.
todo-item/todo-item.resolver.ts
If you are extending the
CRUDResolver directly be sure to register your DTOs with the
NestjsQueryGraphQLModuleWhen using
@InjectAuthorizer, the injected Authorizer is not the CustomAuthorizer, but the DefaultCRUDAuthorizer that
internally uses the CustomAuthorizer. If you want to use the CustomAuthorizer directly, inject it with @InjectCustomAuthorizer
instead.Authorize depending on operation
Sometimes it might be necessary to perform different authorization based on the kind of operation an user wants to execute. E.g. some users could be allowed to read all todo items but only update/delete their own. In this case we can make use of the second parameter of theauthorize function in our CustomAuthorizer or the one passed to the @Authorizer decorator which gets passed additional AuthorizationContext such as the name of the operation that should be authorized:
sub-task/sub-task.authorizer.ts
AuthorizationContext has the following shape:
authorizer.ts
@AuthorizerFilter(), you should pass the context as argument to the decorator:
operationName to let the context use the name of the decorated Method. If you leave out the readonly property, it’s inferred from the operationGroup.
The operationNames of the generated CRUD resolver methods are similar to the ones of the QueryService:
queryManyfindByIdaggregatecreateOnecreateManyupdateOneupdateManydeleteOnedeleteMany
query{PluralRelationName}(e.g. querySubTasks)find{SingularRelationName}(e.g. findTodoItem)aggregate{PluralRelationName}(e.g. aggregateSubTasks)remove{SingularRelationName}from{SingularParentName}(e.g. removeSubTaskFromTodoItem)remove{PluralRelationName}from{SingularParentName}(e.g. removeSubTasksFromTodoItem)set{SingularRelationName}On{SingularParentName}(e.g. setSubTaskOnTodoItem)add{PluralRelationName}On{SingularParentName}(e.g. addSubTasksOnTodoItem)
Complete Example
You can find a complete example in../../../examples/auth
Edit this page