Public routes

Routes that don't require authentication.

What we'll do now is to apply the JwtAuthGuard globally, and on the routes that should be public, we'll add the @Public() decorator, yet to be created.

So, after removing the guard from the getProfile() route, let's go to the AuthModule and add it in its providers globally.

{
  provide: APP_GUARD,
  useClass: JwtAuthGuard,
},

Now the getProfile() route is still protected, but the login() route, which obviously should be public, became inaccessible. Let's then create the file auth -> decorators -> public.decorator.

export const IS_PUBLIC_KEY = 'isPublic';

export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

If we place this decorator over a route, what will happen is that it will have the metadata isPublic set to true. We also use the IS_PUBLIC_KEY constant to be able to access this metadata in a type-safe manner. But how do we actually implement this public behavior?

This will be done inside the JwtAuthGuard. Currently, it just checks if the token is valid. We'll just make it also check first if the isPublic metadata is present in the route. In a positive case, it will instantly allow access. Otherwise, it will continue with its normal flow. Let's then begin.

The first step is to add a constructor that calls super(), so that the guard is initialized normally. But now also adding a Reflector, which allows to access the route's metadata.

constructor(private readonly reflector: Reflector) {
  super();
}

And now we'll override the canActivate() method, which is a guard's method for deciding whether access should be granted or denied.

canActivate(context: ExecutionContext) {
  const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
    context.getHandler(),
    context.getClass(),
  ]);

  if (isPublic) return true;

  return super.canActivate(context);
}

The way to obtain the metadata may seem a bit confusing, but what's happening here is nothing too complicated. If desired, we can use the @Public() decorator over an entire controller, in order to make all its routes public at once. So the guard checks both the controller and the individual routes to collect the metadata.

Here, getHandler() refers to the route and getClass() refers to the controller.

If there was metadata in both the controller and one of its routes, then the one in the route would take precedence, hence the getAllAndOverride() method.

The remainder has already been explained: if isPublic is present, allow access; if not, continue with the normal behavior.

We can now return to the AuthController and add @Public() over the login() route. Now, all the routes of our system require a valid JWT to be accessed, except if declared as public.

Commit - Global JWT guard and decorator for public routes

We can now use the @Public() decorator over some routes where it may make sense, like:

  • find() routes in the ProductsController

  • find() routes in the CategoriesController

  • create() route in the UsersController

Commit - Marking public routes

Last updated