AWS AppSync added support for Lambda authorizers on 30th July 2021 and it made it much easier to implement group-based authorization with 3rd party identity services.

Group-based auth with AppSync and Cognito

I previously wrote about how you can secure multi-tenant applications with AppSync and Cognito. Where you can use custom attributes to capture the tenant ID and use Cognito groups to model the different access levels.

img 61466dc319e2d

This is a simple and effective solution partly because AppSync supports group-based authorization with Cognito out-of-the-box. You can decorate your GraphQL schema with the @aws_auth directive to limit access to those GraphQL operations to users from those groups.

type Mutation { addUser(name: String!, email: AWSEmail!, role: Role!): User @aws_auth(cognito_groups: ["Admin", "SuperUser"]) ...
}

However, this support doesn’t extend to 3rd party identity services (such Auth0 or Okta) if you connect AppSync to them via AppSync’s OpenID Contact (OPENID_CONNECT) authorization mode.

On a recent client project, I opted to use Auth0 as the identity provider because it matched our requirements better. But I still used Cognito with AppSync because we needed group-based authorization and I wanted to leverage the built-in support AppSync has with Cognito.

I was able to connect the Cognito User Pool to Auth0 via SAML federation. The user authentication is therefore handled by Auth0. I used a Lambda function to inject custom attributes (e.g. tenant ID) and groups information into the JWT token on the PreTokenGeneration trigger.

img 61466deb4bb6f

This gave me the best of both worlds:

  • Using Auth0 to manage user authentication and leverage its built-in MFA support and other advanced features.
  • Using Cognito to secure the AppSync API and leverage the built-in group-based authorization.

This set-up works, except for the annoying “2nd login screen” when you use Cognito with SAML federation.

img 61466e025a971

I could connect AppSync to Auth0 with OPENID_CONNECT authorization mode, but then I’d have to implement the group-based authorization logic myself. I could use pipeline resolvers for that, and that’s what pipeline resolvers are designed for to some extend. This sounds great on paper, or when you have only a handful of resolvers. But when you have a non-trivial AppSync API with 100+ resolvers, it takes A LOT of grunt work to rewrite them all to pipeline resolvers. The only sane way to do it (and this is what we did on another project) is to automate the rewrite through a Serverless framework plugin, assuming you’re using the Serverless framework.

Group-based auth with Lambda authorizer

AppSync’s Lambda authorizer works a little differently from API Gateway’s Lambda authorizer. In my opinion, it’s simpler.

An AppSync Lambda authorizer has to return a payload like this to AppSync.

{ "isAuthorized": <true|false>, "resolverContext": {<JSON object, optional>}, "deniedFields": [ "<list of denied fields (ARNs or short names)>" ], "ttlOverride": <optional value in seconds>
}

You can include custom attributes such as tenant ID, etc. in the resolverContext and access these via the resolver as $context.identity.resolverContext.

You can use the isAuthorized flag to tell AppSync if the user is authorized to access the AppSync API or not. But this is not an all or nothing decision. You can use the deniedFields array to specify which operations the user is not allowed to access.

To implement group-based authorization, you need to maintain a list of the GraphQL operations that each group can access. For example, if this was your GraphQL schema (using AppSync with Cognito):

type Query { getSomething(id: ID!): Something getUser(id: ID!): User @aws_auth(cognito_groups: ["Admin", "SuperUser"]) getTenantConfig: TenantConfig @aws_auth(cognito_groups: ["Admin"])
} type Mutation { doSomething(input: DoSomethingInput!): Something addUser(name: String!, email: AWSEmail!, role: Role!): User @aws_auth(cognito_groups: ["Admin", "SuperUser"]) removeUser(id: ID!): User @aws_auth(cognito_groups: ["Admin", "SuperUser"]) configureTenant(input: ConfigureTenantInput!): TenantConfig @aws_auth(cognito_groups: ["Admin"])
}

Here you have three groups of users?—?Admin, SuperUser and everyone else.

In this case, you need to have two arrays, one for Admin and one for SuperUser.

const AdminActions = [ "Query.getUser", "Query.getTenant", "Mutation.addUser", "Mutation.removeUser", "Mutation.configureTenant"
] const SuperUserActions = [ "Query.getUser", "Mutation.addUser", "Mutation.removeUser"
]

These lists contain the actions that only users in those groups can access. We can use these to build up the deniedFields array by:

  1. concatenate all the actions into one list
  2. for each group the user belongs to, remove the associated actions from the list from step 1.

After iterating through all the groups a user belongs to, whatever’s left is what you should return in the deniedFields.

The authorizer function’s response can be cached and you can even override the default TTL setting on a per-request basis.

This is a simple solution to implement and is easy to maintain. And, you don’t have to rewrite all your resolvers as pipeline functions!

When should you use this approach instead of Cognito?

I think the most pertinent decision here is whether you want to use Cognito or another identify provider. I have written about the case for and against Cognito here.

The pricing model for many identity services are not designed for B2C businesses where you have many non-paying, transient users. This is where Cognito’s pricing really shines through. But Cognito lacks many of the features that other identity providers offer out-of-the-box?—?for example, MFA, CAPTCHA, passwordless login flow, etc. Sure, you can build these custom flows yourself using Lambda triggers, but these are very much undifferentiated heavy-lifting that I want the identity provider to handle for me.

Luckily, implementing group-based authorization with 3rd party identity providers have become a lot simpler with the new AppSync Lambda authorizers.

And if you want to learn more about AppSync and GraphQL, then check out my video course – the AppSync Masterclass – and save 30% while we’re still in early access!

Screenshot 2020 08 26 at 14.53.39

Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.

Screenshot 2019 10 19 at 11.44.09

img 5dab0c477fc72

Hi, my name is Yan Cui. I’m an AWS Serverless Hero and the author of Production-Ready Serverless. I specialise in rapidly transitioning teams to serverless and building production-ready services on AWS.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

You can contact me via Email, Twitter and LinkedIn.

Hire me.

The post Group-based auth with AppSync Lambda authoriser appeared first on theburningmonk.com.

OR4dCzfjAe4