Unsubscribing WebSocket connections using filters
In AWS AppSync, you can forcibly unsubscribe and close (invalidate) a WebSocket connection from a connected client based on specific filtering logic. This is useful in authorization-related scenarios such as when you remove a user from a group.
Subscription invalidation occurs in response to a payload defined in a mutation. We
recommend that you treat mutations used to invalidate subscription connections as
administrative operations in your API and scope permissions accordingly by limiting
their use to an admin user, group, or backend service. For example, using schema
authorization directives such as @aws_auth(cognito_groups:
["Administrators"])
or @aws_iam
. For more information, see Using Additional Authorization
Modes.
Invalidation filters use the same syntax and logic as enhanced subscription filters. Define these filters using the following methods:
-
$extensions.invalidateSubscriptions()
– Defined in the GraphQL resolver's response mapping templates of a mutation. -
$extensions.setSubscriptionInvalidationFilter()
– Defined in the GraphQL resolver's response mapping templates of the subscriptions linked to the mutation.
For more information about invalidation filtering extensions, see Resolver mapping template utility reference.
Using subscription invalidation
To see how subscription invalidation works in AWS AppSync, use the following GraphQL schema:
type User { userId: ID! groupId: ID! } type Group { groupId: ID! name: String! members: [ID!]! } type GroupMessage { userId: ID! groupId: ID! message: String! } type Mutation { createGroupMessage(userId: ID!, groupId : ID!, message: String!): GroupMessage removeUserFromGroup(userId: ID!, groupId : ID!) : User @aws_iam } type Subscription { onGroupMessageCreated(userId: ID!, groupId : ID!): GroupMessage @aws_subscribe(mutations: ["createGroupMessage"]) } schema { mutation: Mutation subscription: Subscription }
Define an invalidation filter in the removeUserFromGroup
mutation
response mapping template as shown in the following:
## Response Mapping Template - removeUserFromGroup mutation $extensions.invalidateSubscriptions({ "subscriptionField": "onGroupMessageCreated", "payload": { "userId": $ctx.args.userId, "groupId": $ctx.args.groupId } }) $util.toJson($context.result)
When the mutation is invoked, the data defined in the payload
object
is used to unsubscribe the subscription defined in subscriptionField
.
An invalidation filter is also defined in the onGroupMessageCreated
subscription's response mapping template.
If the $extensions.invalidateSubscriptions()
payload contains an ID
that matches the IDs from the subscribed client as defined in the filter, the
corresponding subscription is unsubscribed. In addition, the WebSocket connection is
closed:
## Response Mapping Template - onGroupMessageCreated subscription $extensions.setSubscriptionFilter({ "filterGroup": [ { "filters" : [ { "fieldName" : "groupId", "operator" : "eq", "value" : $ctx.args.groupId } ] } ] }) $extensions.setSubscriptionInvalidationFilter({ "filterGroup": [ { "filters" : [ { "fieldName" : "userId", "operator" : "eq", "value" : $ctx.args.userId }, { "fieldName" : "groupId", "operator" : "eq", "value" : $ctx.args.groupId } ] ] }) $util.toJson($context.result)
Note that the subscription response template can have both subscription filters and invalidation filters defined at the same time.
For
example,
assume that client A subscribes a new user with the ID
to the group with the ID
user-1
using the following
subscription request:group-1
onGroupMessageCreated(userId : "
user-1
", groupId: :"group-1
"){...}
AWS AppSync runs the subscription resolver, which generates subscription and
invalidation filters as defined in the preceding onGroupMessageCreated
response mapping template. For client A, the subscription filters allow data to be
sent only to
, and the invalidation
filters are defined for both group-1
and
user-1
.group-1
Now assume that client B subscribes a user with the ID
to a group with the ID
user-2
using the following
subscription request:group-2
onGroupMessageCreated(userId : "
user-2
", groupId: :"group-2
"){...}
AWS AppSync runs the subscription resolver, which generates subscription and
invalidation filters. For client B, the subscription filters allow data to be sent
only to
, and the invalidation
filters are defined for both group-2
and
user-2
.group-2
Next, assume that a new group message with the ID
is created using a
mutation request like in the following example:message-1
createGroupMessage(id: "
message-1
", groupId : "group-1
", message: "test message"){...}
Subscribed clients matching the defined filters automatically receive the following data payload via WebSockets:
{ "data": { "onGroupMessageCreated": { "id": "message-1", "groupId": "group-1", "message": "test message", } } }
Client A receives the message because the filtering
criteria
match the defined subscription filter. However, client B doesn't
receive the message, as the user is not part of
. Also, the request doesn't
match the subscription filter defined in the subscription resolver.group-1
Finally, assume that
is removed
from user-1
using the following mutation
request:group-1
removeUserFromGroup(userId: "user-1", groupId : "group-1"){...}
The mutation initiates a subscription invalidation as defined in its
$extensions.invalidateSubscriptions()
resolver response mapping
template method. AWS AppSync then unsubscribes client A and closes its WebSocket
connection. Client B is unaffected, as the invalidation payload defined in the
mutation doesn't match its user or group.
When AWS AppSync invalidates a connection, the client receives a message confirming that they are unsubscribed:
{ "message": "Subscription complete." }
Using context variables in subscription invalidation filters
As with enhanced subscription filters, you can use the $context variable in
the subscription invalidation filter extension to access certain data. For example,
an attribute retrieved by a GetItem
operation from Amazon DynamoDB that's
defined in the same subscription request mapping template
($context.result.severity
), user identity
($context.identity.claims.group
) or, if necessary, specific
arguments from the request ( $context.args.userId
).
It's possible to configure an email address as the invalidation payload in the
mutation, then match it against the email attribute or claim from a subscribed user
authorized with Amazon Cognito user pools or OpenID Connect. The invalidation filter
defined in the $extensions.setSubscriptionInvalidationFilter()
subscription invalidator checks if the email address set by the mutation's
$extensions.invalidateSubscriptions()
payload matches the email
address retrieved from the user's JWT token in
$context.identity.claims.email
, initiating the invalidation.