Use public fields in MJAPI

Using the @Public Directive for Unauthenticated Access

The @Public directive in our system enables unauthenticated access to specific fields within our GraphQL schema. This feature is particularly useful for exposing public-facing data or allowing anonymous users to fetch certain information. In this article, we'll go through how to use the @Public directive, provide code examples, and offer best practices to avoid common errors.

What is the @Public Directive?

The @Public directive is a GraphQL decorator that allows specific fields or operations to bypass authentication. By default, all fields in the system require authentication unless explicitly marked with the @Public decorator. For complex object types, each child field also needs to be independently marked with @Public to avoid authentication errors. This mechanism provides flexibility in building public endpoints for metadata or general information.

Using @Public in Custom Resolvers

To use the @Public directive, you need to apply it to the relevant fields in your custom resolver. Below is an example of how to use @Public with a custom ColorResolver:

import { AppContext } from '../types.js';
import { Ctx, Field, Int, ObjectType, PubSub, PubSubEngine, Query, Resolver, Root, Subscription } from 'type-graphql';
import { Public } from '../directives/index.js';

@ObjectType()
export class Color {
  @Field(() => Int)
  @Public()
  ID: number;

  @Field(() => String)
  @Public()
  name: string;

  @Field(() => String)
  @Public()
  createdZ: string;
}

@ObjectType()
export class ColorNotification {
  @Public()
  @Field(() => String, { nullable: true })
  message?: string;

  @Public()
  @Field(() => Date)
  date!: Date;
}

export interface ColorNotificationPayload {
  message?: string;
}

@Resolver(Color)
export class ColorResolver {
  @Subscription(() => ColorNotification, { topics: 'COLOR' })
  @Public()
  colorSubscription(@Root() { message }: ColorNotificationPayload): ColorNotification {
    return { message, date: new Date() };
  }

  @Query(() => [Color])
  @Public()
  async colors(@Ctx() _ctx: AppContext, @PubSub() pubSub: PubSubEngine) {
    const createdZ = new Date().toISOString();

    pubSub.publish('COLOR', {
      message: 'Colors were requested!',
    });

    return [
      { ID: 1, name: 'Red', createdZ },
      { ID: 2, name: 'Orange', createdZ },
      { ID: 3, name: 'Yellow', createdZ },
      { ID: 4, name: 'Green', createdZ },
      { ID: 5, name: 'Blue', createdZ },
      { ID: 6, name: 'Purple', createdZ },
    ];
  }
}

Explanation

In this example:

  • The Color and ColorNotification classes define GraphQL object types with fields marked by the @Public decorator. This allows these fields (ID, name, createdZ, message, and date) to be accessed without requiring authentication.
  • The ColorResolver class contains two operations:
    • colorSubscription: A subscription to the COLOR topic that publishes messages and dates. It is marked with @Public(), making it accessible to unauthenticated users.
    • colors: A query that returns a list of colors, marked with @Public(), allowing public access to fetch this information.

By using the @Public() decorator, you expose specific fields and operations without requiring the user to log in. However, it's crucial to ensure that every field and child field that you want to be publicly accessible has the @Public decorator to avoid unexpected authentication errors.

Setting Up Custom Resolvers with @Public

When incorporating the @Public directive into your custom resolvers, remember the following:

  1. Add the @Public Decorator: Apply the @Public decorator to the fields, queries, or subscriptions that you want to be accessible without authentication.
  2. Update the [__mj].[Entity] Table: Ensure that the CustomResolverAPI value in the [__mj].[Entity] table is set appropriately (e.g., set it to 1) to enable custom resolver functionality for the entity.
  3. Mark Child Fields: For object types, mark each child field that should be publicly accessible with @Public.

Accessing @Public Fields from an Angular App

Once you’ve set up @Public fields in your custom resolver, you can interact with them in your Angular app. Use the GraphQLDataProvider's ExecuteGQL method to run queries and mutations:

this.graphQLDataProvider.ExecuteGQL({
  query: /* graphql */ `
    query {
      colors {
        ID
        name
        createdZ
      }
    }
  `,
});

In this example, the query fetches data from the colors field, which is marked with the @Public decorator in the ColorResolver. This enables unauthenticated access to this data.

Common Errors and Best Practices

Unauthenticated Errors

If you encounter an "unauthenticated" error:

  • Check that the @Public decorator is correctly applied to all fields you want to expose.
  • Ensure that nested fields (child fields) in object types also have the @Public directive if they should be accessible without authentication.
  • Remove the Authorization header when accessing public fields. If you include an invalid or expired token, the system will still attempt to authenticate and fail.

Manual API Clients

When using manual API clients (e.g., Postman), make sure to manually adjust headers to exclude Authorization when interacting with @Public fields.

Conclusion

The @Public directive is a powerful tool for creating public-facing endpoints in your GraphQL schema. By marking fields, queries, and subscriptions with the @Public decorator, you allow unauthenticated access while retaining the flexibility to restrict access to sensitive information. When using @Public:

  • Apply the directive to every field that should be accessible without authentication.
  • Mark each child field in object types as necessary.
  • Avoid setting the Authorization header if you don't have a valid token.

By following these guidelines, you can effectively use the @Public directive to manage public access in your application.