Skip to main content

@axinom/mosaic-id-guard

The mosaic-id-guard package provides the required middleware and utility related to securing GraphQL endpoints and messages, security permissions, authentication and authorization. These utilities can be used for authentication purposes anywhere within the Mosaic framework.

The exported interfaces, functions, and types are described below.

Interfaces

This section describes the exported interfaces through mosaic-id-guard. These interfaces are used to enforce the data structures required in the library.

EnvironmentInfo

This interface describes a specific environment in Mosaic using tenantId and environmentId.

interface EnvironmentInfo {
tenantId: string;
environmentId: string;
}

SubjectType

SubjectType is an enumeration that is used to identify different types of authenticated tokens. A property of this type named subjectType is available in every Mosaic user token.

enum SubjectType {
ManagedServiceAccount = 'ManagedServiceAccount',
ServiceAccount = 'ServiceAccount',
UserAccount = 'UserAccount',
ImpersonatedUserAccount = 'ImpersonatedUserAccount',
SuperUserAccount = 'SuperUserAccount',
EnvironmentAdminAccount = 'EnvironmentAdminAccount',
EndUserAccount = 'EndUserAccount',
EndUserApplication = 'EndUserApplication',
}

GenericAuthenticatedSubject

This interface abstracts common properties that are included in a JWT. It is the base type for all authenticated subject interfaces.

interface GenericAuthenticatedSubject extends EnvironmentInfo {
/** Issued at (seconds since Unix epoch) */
iat: number;
/** Expiration time (seconds since Unix epoch) */
exp: number;
/** Audience of the token */
aud: string;
/** Issuer of the token (e.g. ax-id-service or ax-user-service) */
iss: string;
/** Subject identifier */
sub: string;
/** Subject name */
name: string;
/** Subject email */
email?: string;
/** Type of the subject */
subjectType: SubjectType;
}

AuthenticatedManagementSubject

This interface describes information related to an authenticated management user/service account. An instance of this object is constructed by parsing the values extracted from the JWT.

interface AuthenticatedManagementSubject extends GenericAuthenticatedSubject {
permissions: {
[key: string]: string[];
};
tags?: string[];
[key: string]: unknown;
}

ManagementAuthenticationContext

This interface describes the properties related to a management user authentication. It has an AuthenticatedManagementSubject if the authentication was successful or a MosaicErrorInfo object if the authentication failed.

interface ManagementAuthenticationContext {
subject?: AuthenticatedManagementSubject;
authErrorInfo?: MosaicErrorInfo;
}

AuthenticatedManagementRequest

This is an extended type of the Express req object with the additional property authContext which is of type ManagementAuthenticationContext.

interface AuthenticatedManagementRequest extends Request {
authContext: ManagementAuthenticationContext;
}

AuthenticatedEndUserApplication

This interface describes an authenticated end-user application token, used when an application-level token (rather than a user-level token) is presented. It is the base type for AuthenticatedEndUser.

interface AuthenticatedEndUserApplication extends GenericAuthenticatedSubject {
applicationId: string;
}

AuthenticatedEndUser

This interface describes information related to an authenticated end-user. An instance of this object is constructed by parsing the values extracted from the JWT created by ax-user-service.

interface AuthenticatedEndUser extends AuthenticatedEndUserApplication {
sessionId: string;
profileId: string;
extensions: unknown;
}

EndUserAuthenticationContext

This interface describes the properties related to end-user authentication. It has an AuthenticatedEndUser or AuthenticatedEndUserApplication if the authentication was successful or a MosaicErrorInfo object if the authentication failed.

interface EndUserAuthenticationContext {
subject?: AuthenticatedEndUser | AuthenticatedEndUserApplication;
authErrorInfo?: MosaicErrorInfo;
}

AuthenticatedEndUserRequest

This is an extended type of the Express req object with the additional property authContext which is of type EndUserAuthenticationContext.

interface AuthenticatedEndUserRequest extends Request {
authContext: EndUserAuthenticationContext;
}

AuthenticatedRequest

A convenience type for request handlers that accept either a management or end-user authenticated request. The authContext can be either a ManagementAuthenticationContext or an EndUserAuthenticationContext.

interface AuthenticatedRequest extends Request {
authContext: ManagementAuthenticationContext | EndUserAuthenticationContext;
}

AuthenticationConfig

This interface describes the information required to access the Identity Service. This is used in utility methods, such as setupManagementAuthentication and setupEndUserAuthentication.

interface AuthenticationConfig {
authEndpoint: string;
tenantId: string;
environmentId: string;
}

PermissionDefinition

This interface is used to construct an object containing permission names along with the GraphQL operations that belong to a permission.

This structure can store an array of Permission objects, and have some configuration properties specially when it comes to GraphQL operations.

  • anonymousGqlOperations - Operations defined under this property are ignored while checking for authorization.
  • ignoredGqlOperations - Operations defined under this property are excluded from "disabled operations" logging. This option can be used when a developer is certain that a GraphQL operation must not be available in the API and therefore explicitly defines it as an ignored operation, so the default behavior of warning about it in the logs will be turned off.
interface Permission {
/**
* A unique identifier for the permission within the service.
*/
key: string;
/**
* A human-readable label for the permission.
*/
title: string;
/**
* When true, this permission is only applicable in managed service environments.
*/
usedByManagedServiceOnly?: boolean;
/**
* When true, this permission is intended for development use only.
*/
usedForDevelopment?: boolean;
/**
* Controls where this permission appears in assignment flows.
* 'ANY' (default) - available in all flows including the user role assignment UI.
* 'SERVICE' - intended for service accounts and hidden from the user role assignment UI.
*/
usageScope?: 'ANY' | 'SERVICE';
/**
* List of GraphQL operations guarded by this permission.
*/
gqlOperations?: readonly string[];
}

interface PermissionDefinition {
gqlOptions?: {
/**
* Operations listed here are ignored during authorization checks and are treated as publicly accessible.
*/
anonymousGqlOperations?: string[];
/**
* Operations listed here are excluded from "disabled operations" logging.
*/
ignoredGqlOperations?: string[];
};
/**
* Array of permissions owned by the service, available for assignment to user roles and service accounts.
*/
permissions: readonly Permission[];
}

The usageScope property controls where a permission appears in assignment flows. ANY (default) makes it available in all flows including the user role assignment UI. SERVICE indicates the permission is intended for service accounts and it is hidden from the user role assignment UI.

Permission Naming Convention

The permissions for the managed services have a naming convention as follows:

  • Key - <PLURAL_ENTITY>_<ACTION>
  • Title - <Plural Entity>: Action

It is advised to follow a similar naming convention when defining customizable service permissions to have better consistency, but is not enforced in anyway.

An example permission definition using this naming convention can be seen below:

export const permissionDefinition: PermissionDefinition = {
gqlOptions: {
anonymousGqlOperations: [M.someOperationWhichCanBeCalledWithoutAnyToken],
},

permissions: [
{
key: 'MOVIES_VIEW',
title: 'Movies: View',
gqlOperations: [...MoviesReadOperations],
},
{
key: 'MOVIES_EDIT',
title: 'Movies: Edit',
gqlOperations: [...MoviesReadOperations, ...MoviesMutateOperations],
}
]
}

Middleware

setupManagementAuthentication

function setupManagementAuthentication(
app: Express,
guardRoutes: string[],
authParams: string | AuthenticationConfig,
): void;

This is the Express middleware that is used by Mosaic services to secure any http endpoints they expose that are related to management users. It validates the Bearer token embedded in the http request header and authorizes the request to use the routes passed as an argument. If a Bearer token is not present for an endpoint guarded by this middleware it throws an AccessTokenRequired error.

In the validation process, it extracts the JWT, verifies its authenticity and sets the authContext of the AuthenticatedManagementRequest. AuthenticatedManagementRequest is an extended type of the Express req object with an additional authContext property. The authContext is an object that carries information related to the authenticated subject of type AuthenticatedManagementSubject.

Usage

The setupManagementAuthentication middleware is called in the index.ts file of Mosaic services that require the authentication functionality.

setupManagementAuthentication(app, ['/graphql'], authenticationConfig);

The middleware function takes the three following arguments:

  • Express app object (app)
  • Guard routes (['/graphql'])
  • A string or an AuthenticationConfig object (authParams)

If a string is passed as the authParams argument, it should contain the ID Service Auth Endpoint. If it is an instance of AuthenticationConfig, it should contain ID Service connection information. In case of non-managed services, it is mandatory to pass an AuthenticationConfig object with the correct tenant and environment IDs.

Since a regular Mosaic backend only exposes GraphQL endpoints, usually, the setupManagementAuthentication middleware is only registered for the /graphql endpoint. However, that does not restrict an application developer from using the middleware to secure any other endpoints that the application might expose.

Errors

This middleware may throw errors with the following codes while validating the JWT:

  • AccessTokenExpired - This error is thrown if the passed JWT has expired
  • SigningKeyNotFound - This error is thrown if a valid signing key is not found to verify the token. This may happen if the signing key used to sign the token is revoked.
  • JwksError - This error is thrown if an error occurs while trying to fetch the signing keys from the JWKS endpoint. One of the reasons this might happen is because the library cannot find a signing key for the kid in the JWT, because the keys were rotated and the user is still trying to access using a JWT signed using the old key. Signing in again to the application may resolve the error.
  • IdentityServiceNotAccessible - This error is thrown if there is a network level error while trying to access the ID Service for verification. i.e. ECONNREFUSED
  • AccessTokenVerificationFailed - This error is thrown if the error does not belong to any of the above categories.

setupEndUserAuthentication

function setupEndUserAuthentication(
app: Express,
guardRoutes: string[],
authParams: string | AuthenticationConfig,
): void;

This is the Express middleware that is used by Mosaic services to secure any http endpoints they expose that are related to end-user related services. It expects a bearer token embedded in the http request header signed by the user service. The function validates the bearer token and authorizes the request to use the routes passed as an argument. If a Bearer token is not present for an endpoint guarded by this middleware it throws an AccessTokenRequired error.

In the validation process, it extracts the JWT, verifies its authenticity and sets the authContext of the AuthenticatedEndUserRequest. AuthenticatedEndUserRequest is an extended type of the Express req object with an additional authContext property. The authContext is an object that carries information related to the authenticated subject of type AuthenticatedEndUser.

Usage

The setupEndUserAuthentication middleware is called in the index.ts file of end-user facing Mosaic services that require the authentication functionality.

setupEndUserAuthentication(app, ['/graphql'], config.userServiceAuthEndpoint);

The middleware function takes the following three arguments:

  • Express app object (app)
  • Guard routes (['/graphql'])
  • A string or an AuthenticationConfig object (authParams)

If a string is passed as the authParams argument, it should contain the User Service Auth Endpoint. If it is an instance of AuthenticationConfig, it should contain User Service connection information. In case of non-managed services, it is mandatory to pass an AuthenticationConfig object with the correct tenant and environment IDs.

In practice, this is called before the call to the setupPostGraphile middleware.

Since a regular Mosaic backend only exposes GraphQL endpoints, usually, the setupEndUserAuthentication middleware is only registered for the /graphql endpoint. However, that does not restrict an application developer from using the middleware to secure any other endpoints that the application might expose.

Errors

This middleware may throw the following errors while validating the JWT:

  • AccessTokenExpired - This error is thrown if the passed JWT has expired
  • SigningKeyNotFound - This error is thrown if a valid signing key is not found to verify the token. This may happen if the signing key used to sign the token is revoked.
  • JwksError - This error is thrown if an error occurs while trying to fetch the signing keys from the JWKS endpoint. One of the reasons this might happen is because the library cannot find a signing key for the kid in the JWT, because the keys were rotated and the user is still trying to access using a JWT signed using the old key. Signing in again to the application may resolve the error.
  • UserServiceNotAccessible - This error is thrown if there is a network level error while trying to access the User Service for verification. i.e. ECONNREFUSED
  • AccessTokenVerificationFailed - This error is thrown if the error does not belong to any of the above categories.

setupManagementGQLSubscriptionAuthentication

setupManagementGQLSubscriptionAuthentication = (
authParams: string | AuthenticationConfig,
): Middleware<Request, Response>[];

This returns an array of Express middleware that is used by Mosaic services for setting up authentication for GraphQL subscriptions that are related to management services.

The middleware returned by this function first extracts the bearer token from the request, parses the token to verify it and attaches it to the authContext property of the request as an ManagementAuthenticationContext. If the authErrorInfo property has a value then that error is thrown.

Usage

setupManagementGQLSubscriptionAuthentication should be passed as websocketMiddleware when setting up web socket server using setupHttpServerWithWebsockets().

const httpServer = setupHttpServerWithWebsockets(
app,
logger,
setupManagementGQLSubscriptionAuthentication(
config.idServiceAuthEndpointUrl,
),
);

This function takes a single argument of either a string which contains the URL of the ID Service auth endpoint or an instance of AuthenticationConfig containing ID Service connection information.

Errors

This middleware may throw an AxGuardError if the token verification fails.

setupEndUserGQLSubscriptionAuthentication

setupEndUserGQLSubscriptionAuthentication = (
authParams: string | AuthenticationConfig,
): Middleware<Request, Response>[];

This returns an array of Express middleware that is used by Mosaic services for setting up authentication for GraphQL subscriptions that are related to end-user applications.

The middleware returned by this function first extracts the bearer token from the request, parses the token to verify it and attaches it to the authContext property of the request as an EndUserAuthenticationContext. If the authErrorInfo property has a value then that error is thrown.

Usage

setupEndUserGQLSubscriptionAuthentication should be passed as websocketMiddleware when setting up web socket server using setupHttpServerWithWebsockets().

const httpServer = setupHttpServerWithWebsockets(
app,
logger,
setupEndUserGQLSubscriptionAuthentication(
config.userServiceAuthEndpointUrl,
),
);

This function takes a single argument of either a string which contains the URL of the User Service auth endpoint or an instance of AuthenticationConfig containing User Service connection information.

Errors

This middleware may throw an AxGuardError if the token verification fails.

Functions

The following section describes the functions that are exported through this library as well as their usages.

getAuthenticatedManagementSubject

This function validates the specified token in the context of a given environment and derives an AuthenticatedManagementSubject object (which contains a list of the permissions provided in the token).

getAuthenticatedManagementSubject = async (
token: string,
authParams: string | AuthenticationConfig,
): Promise<AuthenticatedManagementSubject>

getAuthenticatedEndUser

This function can be used to verify and derive an AuthenticatedEndUser or AuthenticatedEndUserApplication object from a JWT.

getAuthenticatedEndUser = async (
token: string,
authParams: string | AuthenticationConfig,
): Promise<AuthenticatedEndUser | AuthenticatedEndUserApplication>

getManagementAuthenticationContext

This function accepts an http request and an optional string or AuthenticationConfig and returns the ManagementAuthenticationContext for that request.

getManagementAuthenticationContext = async (
req: Request,
authParams?: string | AuthenticationConfig,
): Promise<ManagementAuthenticationContext>

getEndUserAuthenticationContext

This function accepts an http request and an optional string or AuthenticationConfig and returns the EndUserAuthenticationContext for that request.

getEndUserAuthenticationContext = async (
req: Request,
authParams?: string | AuthenticationConfig,
): Promise<EndUserAuthenticationContext>

PostGraphile Plug-ins

The PostGraphile plug-ins are used to extend the GraphQL schema that is generated by PostGraphile.

AxGuardPlugin

The AxGuardPlugin is a Postgraphile wrapper plug-in which should be added to the appendPlugins array in the PostGraphile options object when setting up PostGraphile. This plug-in takes care of the authorization functionality and makes sure that the JWT has the required permissions to access the given GraphQL resource. The authorization logic applies to queries, mutations and subscriptions.

In addition to performing authorization logic, this plugin generates a permission-definition.json file when the configuration is set to development. This file contains the Permission Definition for the service. If this file needs to be generated, the AxGuard plugin must be provided with two additional arguments:

  • config - The configuration object of the service.
  • permissionDefinitionJsonPath - The path to the permission-definition.json file.

Setting up PostGraphile

The PostGraphile options can be set up in two ways:

  • Construct the PostGraphile options as mentioned here.
  • Use the PostgraphileOptionsBuilder exposed via @axinom/mosaic-service-common to construct the PostGraphile options object.

Usage

note

Extra code has been removed from the examples below for readability. All examples use the PostgraphileOptionsBuilder to construct the PostGraphile options.

The plug-in is added to the PostGraphile options as shown below.

PostgraphileOptionsBuilder(config.isDev, config.graphqlGuiEnabled)
.addPlugins(
AxGuardPlugin(config, './src/generated/security/permission-definition.json'),
)
.build();

There are a few prerequisites for the AxGuardPlugin to work, which need to be set in the PostGraphile options.

The AuthenticatedManagementSubject or AuthenticatedEndUser must be set in the ExtendedGraphQLContext. It can be done as shown below.

PostgraphileOptionsBuilder(config.isDev, config.graphqlGuiEnabled)
.setAdditionalGraphQLContextFromRequest(async (req) => {
const { subject, authErrorInfo } = await getManagementAuthenticationContext(
req,
config.idServiceAuthEndpointUrl,
);
return {
subject,
authErrorInfo,
};
})
.build();

The serviceId and permissionDefinition properties must be set in the PostGraphile options. The permissionDefinition object is of the type PermissionDefinition, which contains the mapping of permissions to GraphQL operations.

The following code snippet describes how serviceId and permissionDefinition are set.

PostgraphileOptionsBuilder(config.isDev, config.graphqlGuiEnabled)
.addGraphileBuildOptions({
serviceId: config.serviceId,
permissionDefinition: permissionDefinition,
})
.build();

AxGuardPlugin is a wrapper plug-in. For any GraphQL request, after a request is authenticated through setupManagementAuthentication or setupEndUserAuthentication middleware, this plug-in comes into action and performs the required authorization tasks. The plug-in is executed before the rest of the request is processed.

When performing the authorization, it excludes any operations that are defined under anonymousGqlOperations in the permissionDefinition. Then, it extracts the permissions attached to the AuthenticatedManagementSubject object and validates against the permissionDefinition object to check if the required permissions are present.

Errors

If the required permissions are not present in the AuthenticatedManagementSubject or AuthenticatedEndUser to perform the requested GraphQL operation, the plug-in throws the UserNotAuthorized error.

EnforceStrictPermissionsPlugin

The EnforceStrictPermissionsPlugin omits all GraphQL operations from being exposed that are not assigned to a permission in the respective PermissionDefinition file. This excludes any operations defined under anonymousGqlOperations.

Usage

This plug-in requires the permissionDefinition property in the PostGraphile options to be set (see above).

Then, the plug-in must added to the appendPlugins array in the PostGraphile options, so it could be enabled.

PostgraphileOptionsBuilder(config.isDev, config.graphqlGuiEnabled)
.addPlugins(
EnforceStrictPermissionsPlugin,
)
.build();

When the service is started, this plug-in logs any disabled operations and mapped operations that do not exist.

Classes

ManagedServiceGuardedMessageHandler

The ManagedServiceGuardedMessageHandler is an abstract class that can be used to build message handlers for Axinom Managed Services. When extended, it guards the handler by extracting and verifying the JWT token from the incoming message and validating that the required permissions are present.

note

This class is only intended to be used by Axinom Managed Services. For customizable services, use GuardedTransactionalInboxMessageHandler instead.

Usage

As a practice/pattern, it is recommended that a service-specific class is defined extending from ManagedServiceGuardedMessageHandler and that this class is used for handler creation.

// Service-specific base class
abstract class MyServiceGuardedMessageHandler<
TContent
> extends ManagedServiceGuardedMessageHandler<TContent> {
constructor(
messagingKey: string,
permissions: string[],
protected readonly config: Config,
overrides?: SubscriptionConfig,
middleware: OnMessageMiddleware[] = [],
) {
super(
messagingKey,
permissions,
config.serviceId,
config.idServiceAuthBaseUrl,
overrides,
middleware,
);
}
}

// Message handler extending from the service-specific base class
class PublishEntityCommandHandler extends MyServiceGuardedMessageHandler<
PublishEntityCommand
> {
private readonly logger;

constructor(
protected readonly broker: Broker,
private readonly dbPool: Pool,
config: Config,
) {
super(
MediaMessagingSettings.PublishEntity.messageType,
[
'ADMIN',
'COLLECTIONS_EDIT',
'MOVIES_EDIT',
'SETTINGS_EDIT',
'TVSHOWS_EDIT',
],
config,
);

this.logger = new Logger(config, 'PublishEntityCommandHandler');
}

async onMessage(
payload: PublishEntityCommand,
messageInfo: MessageInfo<PublishEntityCommand>,
): Promise<void> {
//....
}
}

GuardedTransactionalInboxMessageHandler

GuardedTransactionalInboxMessageHandler uses two supporting types:

GuardedConfig — the minimum config shape required by the handler. Your service config must satisfy this type to be passed as TConfig.

type GuardedConfig = BasicDBConfig & BasicConfig & { idServiceAuthBaseUrl: string };

GuardedContext — the context object produced after successful authentication and authorization, made available to setPgSettings and the handler logic.

interface GuardedContext {
subject: AuthenticatedManagementSubject;
[key: string]: unknown;
}

The GuardedTransactionalInboxMessageHandler is an abstract class for building transactional inbox message handlers with authentication and authorization. It extends TransactionalInboxMessageHandler and guards the handler by verifying the JWT token embedded in the message metadata and validating the required permissions.

When extending this class, the setPgSettings method must be implemented to configure the PostgreSQL connection context for the authenticated subject.

Usage

As with ManagedServiceGuardedMessageHandler, the recommended pattern is to define a service-specific base class:

// Service-specific base class
abstract class MyServiceGuardedTransactionalInboxMessageHandler<
T,
> extends GuardedTransactionalInboxMessageHandler<T, Config> {
constructor(
messagingSettings: MessagingSettings,
permissions: string[],
logger: Logger,
config: Config,
) {
super(
messagingSettings,
permissions,
logger,
config,
config.idServiceAuthBaseUrl,
);
}

protected override async setPgSettings(
envOwnerClient: DatabaseClient,
subject: AuthenticatedManagementSubject,
): Promise<void> {
const pgSettings = buildAuthPgSettings(subject, this.config.serviceId);
await setPgSettingsConfig(pgSettings, envOwnerClient);
}
}

Was this page helpful?