Authorization
Authorization controls who can access what data in your GraphQL API. Type GraphQL Query provides powerful authorization capabilities through the @Authorize() decorator with support for roles, permissions, attributes, and complex authorization expressions.
What You’ll Learn
Section intitulée « What You’ll Learn »This guide will help you understand:
- ✅ How to use the
@Authorize()decorator - ✅ How to implement role-based, permission-based, and attribute-based authorization
- ✅ How to combine authorizations with AND, OR, and NOT operators
- ✅ How to configure authorization at resolver, field, and class levels
- ✅ How to create custom authorization checkers
- ✅ How to configure the authorization system
Overview
Section intitulée « Overview »Authorization is implemented using decorators and middleware. Type GraphQL Query provides the @Authorize() decorator which extends TypeGraphQL’s @Authorized() with support for complex authorization expressions.
import { Query, Resolver } from '@lib/type-graphql';import { Authorize } from '@lib/type-graphql-query';import { Product } from './product';
@Resolver()export class ProductResolver { @Authorize() @Query(() => [Product]) async products(): Promise<Product[]> { // Only authenticated users can access }}The Authorize Decorator
Section intitulée « The Authorize Decorator »The @Authorize() decorator is the main way to protect your GraphQL resolvers and fields. It accepts either a string expression or an Authorization object.
Basic Usage
Section intitulée « Basic Usage »import { Authorize } from '@lib/type-graphql-query';import { Query, Resolver } from '@lib/type-graphql';
@Resolver()export class ProductResolver { // Require authentication only @Authorize() @Query(() => [Product]) async products(): Promise<Product[]> { return []; }
// Require specific role @Authorize("role('admin')") @Query(() => [Product]) async adminProducts(): Promise<Product[]> { return []; }
// Require specific permission @Authorize("permission('view_products')") @Query(() => [Product]) async viewProducts(): Promise<Product[]> { return []; }}Using Authorization Objects
Section intitulée « Using Authorization Objects »You can use the authorization factory functions for type-safe and readable authorization:
import { Authorize, Role, Permission, AllOf, AnyOf, Not } from '@lib/type-graphql-query';import { Query, Resolver, Mutation } from '@lib/type-graphql';
@Resolver()export class ProductResolver { // Single role @Authorize(Role('admin')) @Query(() => [Product]) async adminProducts(): Promise<Product[]> { return []; }
// Single permission @Authorize(Permission('view_products')) @Query(() => [Product]) async viewProducts(): Promise<Product[]> { return []; }
// Multiple roles (OR) @Authorize(AnyOf(Role('admin'), Role('moderator'))) @Query(() => [Product]) async adminOrModeratorProducts(): Promise<Product[]> { return []; }
// Combined requirements (AND) @Authorize(AllOf(Role('admin'), Permission('view_products'))) @Query(() => [Product]) async adminWithPermission(): Promise<Product[]> { return []; }
// Negation (NOT) @Authorize(Not(Role('guest'))) @Mutation(() => Product) async createProduct(): Promise<Product> { // Anyone except guests can create }
// Complex authorization @Authorize( AllOf( AnyOf(Role('admin'), Role('moderator')), Permission('manage_products'), Not(AnyOf(Role('banned'), Attribute('is_suspended'))), ), ) @Mutation(() => Product) async updateProduct(): Promise<Product> { // Must be admin or moderator // AND have manage_products permission // AND NOT be banned or suspended }}String Expressions
Section intitulée « String Expressions »You can also use string expressions directly. When using string expressions, you can either specify the authorization type explicitly or rely on defaultAuthorizationType:
@Resolver()export class ProductResolver { // Simple string expression with explicit function @Authorize("role('admin')") @Query(() => [Product]) async adminProducts(): Promise<Product[]> { return []; }
// Simple string expression without function (uses defaultAuthorizationType) // If defaultAuthorizationType is 'permission', this is equivalent to: // @Authorize("permission('product.view')") @Authorize('product.view') @Query(() => [Product]) async viewProducts(): Promise<Product[]> { return []; }
// Complex string expression with AND @Authorize("(role('admin') AND permission('view_products'))") @Query(() => [Product]) async complexQuery(): Promise<Product[]> { return []; }
// Complex string expression with OR @Authorize("(role('admin') OR role('moderator'))") @Query(() => [Product]) async adminOrModerator(): Promise<Product[]> { return []; }
// Complex string expression with NOT @Authorize("!(role('guest'))") @Mutation(() => Product) async createProduct(): Promise<Product> { return {}; }}Field-Level Authorization
Section intitulée « Field-Level Authorization »You can protect individual fields in your GraphQL types:
import { ObjectType, Field } from '@lib/type-graphql';import { Authorize, Role, Permission } from '@lib/type-graphql-query';
@ObjectType()export class Product { @Field() id!: string;
@Field() name!: string;
// Only admins can see the cost @Authorize(Role('admin')) @Field() cost!: number;
// Users with view_prices permission can see the price @Authorize(Permission('view_prices')) @Field() price!: number;}Class-Level Authorization
Section intitulée « Class-Level Authorization »You can apply authorization at the resolver class level:
import { Resolver, Query } from '@lib/type-graphql';import { Authorize, Role } from '@lib/type-graphql-query';
@Resolver()@Authorize(Role('admin')) // All methods in this resolver require admin roleexport class AdminResolver { @Query(() => String) async adminOnly(): Promise<string> { return 'Admin only'; }
// You can override class-level authorization @Authorize(Role('super_admin')) @Query(() => String) async superAdminOnly(): Promise<string> { return 'Super admin only'; }}Custom Authorization
Section intitulée « Custom Authorization »You can implement custom authorization logic:
@Authorized()@Query(() => Product)async product( @Args("id") id: string, @Ctx() ctx: Context): Promise<Product> { // Custom authorization logic if (ctx.user.role !== "ADMIN" && product.userId !== ctx.user.id) { throw new Error("Unauthorized"); } return product;}Authorization Types
Section intitulée « Authorization Types »Type GraphQL Query provides three predefined authorization types:
permission- Checks if the user has specific permissionsrole- Checks if the user has specific rolesattribute- Checks if the user has specific attributes
These authorization types correspond to the properties returned by getUserAuthorizationsFor:
permissiontype →permissionsproperty (array of strings)roletype →rolesproperty (array of strings)attributetype →attributesproperty (array of strings)
Authorization Factory Functions
Section intitulée « Authorization Factory Functions »Type GraphQL Query provides factory functions to create type-safe authorization objects:
Creates a role-based authorization:
import { Role } from "@lib/type-graphql-query";
@Authorize(Role("admin"))@Query(() => [Product])async adminProducts(): Promise<Product[]> { return [];}Permission
Section intitulée « Permission »Creates a permission-based authorization:
import { Permission } from "@lib/type-graphql-query";
@Authorize(Permission("view_products"))@Query(() => [Product])async viewProducts(): Promise<Product[]> { return [];}Attribute
Section intitulée « Attribute »Creates an attribute-based authorization:
import { Attribute } from "@lib/type-graphql-query";
@Authorize(Attribute("can_delete"))@Mutation(() => Product)async deleteProduct(): Promise<Product> { return {};}AllOf (AND)
Section intitulée « AllOf (AND) »Combines multiple authorizations with AND logic:
import { AllOf, Role, Permission } from "@lib/type-graphql-query";
@Authorize(AllOf( Role("admin"), Permission("manage_products")))@Mutation(() => Product)async createProduct(): Promise<Product> { // User must have BOTH admin role AND manage_products permission return {};}AnyOf (OR)
Section intitulée « AnyOf (OR) »Combines multiple authorizations with OR logic:
import { AnyOf, Role } from "@lib/type-graphql-query";
@Authorize(AnyOf( Role("admin"), Role("moderator")))@Query(() => [Product])async products(): Promise<Product[]> { // User must have admin role OR moderator role return [];}Negates an authorization:
import { Not, Role } from "@lib/type-graphql-query";
@Authorize(Not(Role("guest")))@Mutation(() => Product)async createProduct(): Promise<Product> { // User must NOT have guest role return {};}Complex Combinations
Section intitulée « Complex Combinations »You can combine these functions for complex authorization logic:
import { AllOf, AnyOf, Not, Role, Permission } from "@lib/type-graphql-query";
@Authorize(AllOf( // Must be admin OR moderator AnyOf(Role("admin"), Role("moderator")), // AND have manage_products permission Permission("manage_products"), // AND NOT be banned Not(Role("banned"))))@Mutation(() => Product)async updateProduct(): Promise<Product> { // Complex authorization: (admin OR moderator) AND manage_products AND NOT banned return {};}AuthOptions Configuration
Section intitulée « AuthOptions Configuration »The AuthOptions<TContextType> interface allows you to configure the authorization system when building your GraphQL schema.
Basic Configuration
Section intitulée « Basic Configuration »import { buildSchema } from '@lib/type-graphql-query';import type { AuthOptions } from '@lib/type-graphql-query';
interface MyContext { user?: { id: string; roles: string[]; permissions: string[]; };}
const authOptions: AuthOptions<MyContext> = { defaultAuthorizationType: 'permission', getUserAuthorizationsFor: async ({ resolverData, context, authorizations }) => { const user = resolverData.context.user;
if (!user) { return false; // User not authenticated }
// Return only the authorizations needed for this check return { roles: user.roles, permissions: user.permissions, }; },};
const schema = await buildSchema({ // ... other options auth: authOptions,});Properties
Section intitulée « Properties »defaultAuthorizationType
Section intitulée « defaultAuthorizationType »The default authorization type to use when a string expression doesn’t specify a function. This allows you to write shorter expressions without explicitly specifying the authorization type.
When you write @Authorize('product.view'), it’s equivalent to @Authorize("permission('product.view')") or @Authorize(Permission('product.view')) if defaultAuthorizationType is set to 'permission'.
The function name in string expressions (like permission, role, attribute) must match one of the available authorization types. The defaultAuthorizationType is only used when no function is specified in the expression.
Example:
const authOptions: AuthOptions<MyContext> = { defaultAuthorizationType: 'permission', // Default to 'permission'};
// These are equivalent:@Authorize('product.view')@Authorize("permission('product.view')")@Authorize(Permission('product.view'))Changing the default type:
const authOptions: AuthOptions<MyContext> = { defaultAuthorizationType: 'role', // Use 'role' as default instead of 'permission'};
// Now these are equivalent:@Authorize('admin')@Authorize("role('admin')")@Authorize(Role('admin'))Note: The defaultAuthorizationType must be a valid authorization type that exists in your system. The predefined types are 'permission', 'role', and 'attribute'. You can also define custom authorization types.
getUserAuthorizationsFor
Section intitulée « getUserAuthorizationsFor »A function that retrieves the current user’s authorizations for a specific scope. This function is optimized to fetch only the authorizations needed for the current check, not all user authorizations.
Parameters:
resolverData- GraphQL resolver datacontext- Execution contextauthorizations- Authorizations to check (scope of the verification)
Returns:
Authorizations- The user’s authorizations in the requested scope. This object should contain properties matching the authorization types:permissions- Array of permission strings (forpermissiontype)roles- Array of role strings (forroletype)attributes- Array of attribute strings (forattributetype)
false- If the user is not authenticated
Authorization Type Mapping:
- Authorization type
permissionuses thepermissionsproperty (plural) - Authorization type
roleuses therolesproperty (plural) - Authorization type
attributeuses theattributesproperty (plural)
Example:
const authOptions: AuthOptions<MyContext> = { getUserAuthorizationsFor: async ({ resolverData, context, authorizations }) => { const user = resolverData.context.user;
if (!user) { return false; }
// Only fetch the authorizations needed for this specific check // This optimizes database queries const userAuthorizations: Authorizations = {};
// Check if roles are needed (for 'role' authorization type) if (authorizations.roles) { userAuthorizations.roles = await getUserRoles(user.id, authorizations.roles); }
// Check if permissions are needed (for 'permission' authorization type) if (authorizations.permissions) { userAuthorizations.permissions = await getUserPermissions( user.id, authorizations.permissions, ); }
// Check if attributes are needed (for 'attribute' authorization type) if (authorizations.attributes) { userAuthorizations.attributes = await getUserAttributes(user.id, authorizations.attributes); }
return userAuthorizations; },};authorizationCheckers
Section intitulée « authorizationCheckers »Custom authorization checkers for specific authorization types. Allows you to implement custom logic for different authorization types.
The isAuthorized function passed to the checker can be used to check other authorizations (not the one being checked, as that would create an infinite loop). This is particularly useful when implementing complex authorization logic, such as the AllOf operator which calls isAuthorized on each element of the authorizations array.
Critical: Never call isAuthorized(authorization) with the same authorization parameter that the checker is currently processing. This will create an infinite loop because isAuthorized calls the checker, which would call isAuthorized again with the same authorization, and so on.
Use isAuthorized only to check different authorizations (e.g., when checking nested authorizations like AllOf(Role('admin'), Permission('view'))).
Example: Custom Checker for a Single Type
import type { AuthorizationChecker, Authorization } from '@lib/type-graphql-query';
const customChecker: AuthorizationChecker<MyContext> = ({ resolverData, context, authorizations, authorization, isAuthorized,}) => { // Custom logic for checking authorization // No need to check authorization.type since this checker is only for 'custom' type const customValue = authorization.value as string; return checkCustomAuthorization(customValue, context);};
const authOptions: AuthOptions<MyContext> = { authorizationCheckers: { custom: customChecker, },};Example: Custom Checker for Multiple Types
If your checker handles multiple authorization types, you need to check the type:
import type { AuthorizationChecker, Authorization } from '@lib/type-graphql-query';
const multiTypeChecker: AuthorizationChecker<MyContext> = ({ resolverData, context, authorizations, authorization, isAuthorized,}) => { if (authorization.type === 'custom') { // Handle custom type const customValue = authorization.value as string; return checkCustomAuthorization(customValue, context); }
if (authorization.type === 'attribute') { // Handle attribute type const attributeValue = authorization.value as string; return authorizations.attributes?.includes(attributeValue) ?? false; }
// For other types, use default behavior (check directly in authorizations) // DO NOT call isAuthorized(authorization) here as it would create an infinite loop const authKey = `${authorization.type}s`; return authorizations[authKey]?.includes(authorization.value as string) ?? false;};
const authOptions: AuthOptions<MyContext> = { authorizationCheckers: { custom: multiTypeChecker, attribute: multiTypeChecker, // Same checker for multiple types },};Example: Using isAuthorized for Complex Logic
The isAuthorized function is useful when you need to check nested authorizations:
import type { AuthorizationChecker, Authorization } from '@lib/type-graphql-query';import { AllOf, Role, Permission } from '@lib/type-graphql-query';
const complexChecker: AuthorizationChecker<MyContext> = ({ resolverData, context, authorizations, authorization, isAuthorized,}) => { if (authorization.type === 'custom') { const customValue = authorization.value as string;
// Use isAuthorized to check other authorizations // This is safe because we're checking different authorizations if (customValue === 'admin_with_permission') { // Check if user has admin role AND specific permission return isAuthorized(AllOf(Role('admin'), Permission('manage_products'))); }
return checkCustomAuthorization(customValue, context); }
// For other types, use default behavior const authKey = `${authorization.type}s`; return authorizations[authKey]?.includes(authorization.value as string) ?? false;};
const authOptions: AuthOptions<MyContext> = { authorizationCheckers: { custom: complexChecker, },};Complete Example
Section intitulée « Complete Example »import { buildSchema } from '@lib/type-graphql-query';import type { AuthOptions, AuthorizationChecker } from '@lib/type-graphql-query';
interface MyContext { user?: { id: string; roles: string[]; permissions: string[]; attributes: string[]; };}
const attributeChecker: AuthorizationChecker<MyContext> = ({ authorization, authorizations, context,}) => { // Check if user has the required attribute const requiredAttribute = authorization.value as string; return authorizations.attributes?.includes(requiredAttribute) ?? false;};
const authOptions: AuthOptions<MyContext> = { defaultAuthorizationType: 'permission',
authorizationCheckers: { attribute: attributeChecker, },
getUserAuthorizationsFor: async ({ resolverData, context, authorizations }) => { const user = resolverData.context.user;
if (!user) { return false; }
// Optimize: only fetch what's needed // The property names (roles, permissions, attributes) correspond to // the authorization types (role, permission, attribute) const result: Authorizations = {};
// For 'role' authorization type → use 'roles' property if (authorizations.roles?.length) { result.roles = user.roles.filter((r) => authorizations.roles!.includes(r)); }
// For 'permission' authorization type → use 'permissions' property if (authorizations.permissions?.length) { result.permissions = user.permissions.filter((p) => authorizations.permissions!.includes(p)); }
// For 'attribute' authorization type → use 'attributes' property if (authorizations.attributes?.length) { result.attributes = user.attributes.filter((a) => authorizations.attributes!.includes(a)); }
return result; },};
const schema = await buildSchema({ resolvers: [ProductResolver, UserResolver], auth: authOptions,});Quick Reference
Section intitulée « Quick Reference »| Feature | Description | Example |
|---|---|---|
| Role | Role-based authorization | @Authorize(Role("admin")) |
| Permission | Permission-based authorization | @Authorize(Permission("view_products")) |
| Attribute | Attribute-based authorization | @Authorize(Attribute("can_delete")) |
| AllOf | AND logic | @Authorize(AllOf(Role("admin"), Permission("view"))) |
| AnyOf | OR logic | @Authorize(AnyOf(Role("admin"), Role("moderator"))) |
| Not | NOT logic | @Authorize(Not(Role("guest"))) |
Next Steps
Section intitulée « Next Steps »Continue learning:
- 🔧 Resolvers - Resolver implementation
- 🔍 Queries - Query operations
- ✏️ Mutations - Mutation operations
- 🎯 ContextFilter - Context-aware filtering
Related Topics
Section intitulée « Related Topics »- Type GraphQL Query Overview - Complete overview
- Resolvers - Resolver implementation
- Queries - Query operations
- API - Permissions - API permissions documentation