@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
stringor anAuthenticationConfigobject (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 expiredSigningKeyNotFound- 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 thekidin 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.ECONNREFUSEDAccessTokenVerificationFailed- 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
stringor anAuthenticationConfigobject (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 expiredSigningKeyNotFound- 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 thekidin 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.ECONNREFUSEDAccessTokenVerificationFailed- 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 thepermission-definition.jsonfile.
Setting up PostGraphile
The PostGraphile options can be set up in two ways:
- Construct the PostGraphile options as mentioned here.
- Use the
PostgraphileOptionsBuilderexposed via@axinom/mosaic-service-commonto construct the PostGraphile options object.
Usage
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.
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);
}
}