Front-End Web & Mobile

Using multiple authorization types with AWS AppSync GraphQL APIs

Written by Ionut Trestian, Min Bi, Vasuki Balasubramaniam, Karthikeyan, Manuel Iglesias, BG Yathi Raj, and Nader Dabit

Today, AWS announced that AWS AppSync now supports configuring more than one authorization type for GraphQL APIs. You can now configure a single GraphQL API to deliver private and public data. Private data requires authenticated access using authorization mechanisms such as IAM, Amazon Cognito User Pools, and OIDC. Public data does not require authenticated access and is delivered through authorization mechanisms such as API Keys.

You can also configure a single GraphQL API to deliver private data using more than one authorization type. For example, you can configure your GraphQL API to authorize some schema fields using OIDC, while other schema fields through Amazon Cognito User Pools or IAM.

AWS AppSync is a managed GraphQL service that simplifies application development. It allows you to create a flexible API to securely access, manipulate, and combine data from one or more data sources.

With today’s launch, you can configure additional authorization types while retaining the authorization settings of your existing GraphQL APIs. To ensure that there are no behavioral changes in your existing GraphQL APIs, your current authorization settings are set as the default.  You can add additional authorization types using the AWS AppSync console, AWS CLI, or AWS CloudFormation templates.

To add more authorization types using the AWS AppSync console, launch the console, choose your GraphQL API, then choose Settings and scroll to the Authorization settings. The snapshot below shows a GraphQL API configured to use API Key as the default authorization type. It also has two Amazon Cognito user pools and AWS IAM as additional authorization types.

  • To add more authorization types using the AWS CLI, see the create-graphql-api section of the AWS CLI Command Reference.
  • To add more authorization types through AWS CloudFormation, see AWS::AppSync::GraphQLApi in the AWS CloudFormation User Guide.
  • As of this post, the AWS Amplify CLI GraphQL Transform library does not yet support multiple authorization types though it is on the roadmap & being worked on.

After configuring the authorization types for your GraphQL API, you can use schema directives to set the authorization types for one or more fields in your GraphQL schema. AWS AppSync now supports the following schema directives for authorization:

  • @aws_api_key to—A field uses API_KEY for authorization.
  • @aws_iam—A field uses AWS_IAM for authorization.
  • @aws_oidc—A field uses OPENID_CONNECT for authorization.
  • @aws_cognito_user_pools—A field uses AMAZON_COGNITO_USER_POOLS for authorization.

The following code example shows using schema directives for authorization:

schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id: ID): Post
    getAllPosts(): [Post]
    @aws_api_key
}

type Mutation {
    addPost(
        id: ID!
        author: String!
        title: String!
        content: String!
        url: String!
    ): Post!
}

type Post @aws_api_key @aws_iam {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
}

Assume that AWS_IAM is the default authorization type for this GraphQL schema. This means that fields without directives are protected using AWS_IAM. An example is the getPost() field in Query.

Next, look at the getAllPosts() field in Query. This field is protected using @aws_api_key, which means that you can access this field using API keys. Directives work at the field level. This means that you must give API_KEY access to the Post type as well. This can be done in two ways:

  • Mark each field in the Post type with a directive.
  • Mark the Post type itself with the @aws_api_key directive.

For this example, I chose the latter option.

Now, to restrict access to fields in the Post type, you can configure directives for individual fields, as shown below. You can add a field called restrictedContent to Post and restrict access to it by using the @aws_iam directive. With this setup, AWS_IAM requests can access restrictedContent, while requests with API keys do not have access.

type Post @aws_api_key @aws_iam {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    restrictedContent: String!
    @aws_iam
}

Amplify CLI

As of this post, the AWS Amplify CLI GraphQL Transform library does not yet support multiple authorization types though it is on the roadmap & being worked on.

Amplify CLI version 1.6.8 supports adding AWS AppSync APIs configured with multiple authorization types. To add an API with mixed authorization mode, you can run the following command:

$ amplify add codegen —apiId <API_ID>

✖ Getting API details
✔ Getting API details
Successfully added API to your Amplify project
? Enter the file name pattern of graphql queries, mutations and subscriptions graphql/**/*.graphql
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
? Enter the file name for the generated code API.swift
? Do you want to generate code for your newly created GraphQL API Yes
✔ Downloaded the schema
✔ Generated GraphQL operations successfully and saved at graphql
✔ Code generated successfully and saved in file API.swift

Android & iOS client support

AWS also updated the Android and iOS clients to support multiple authorization types. You can enable multiple clients by setting the useClientDatabasePrefix flag to true. The awsconfiguration.json file is generated by the AWS AppSync console, and the Amplify CLI adds an entry in the AWS AppSync section. This is used to separate the caches used for operations such as query, mutation, and subscription.

Important: If you are an existing client, the useClientDatabasePrefix flag has a default value of false.  When you use multiple clients, setting useClientDatabasePrefix to true changes the location of the caches used by the client. You also must migrate any data within the caches to keep.

The following code examples highlight the new values in the awsconfiguration.json and the client code configurations.

awsconfiguration.json

The friendly_name illustrated here is created from a prompt from the Amplify CLI. There are four clients in this configuration that connect to the same API, except that they use different AuthMode and ClientDatabasePrefix settings.

{
  "Version": "1.0",
  "AppSync": {
    "Default": {
      "ApiUrl": "https://xyz.us-west-2.amazonaws.com/graphql",
      "Region": "us-west-2",
      "AuthMode": "API_KEY",
      "ApiKey": "da2-xyz",
      "ClientDatabasePrefix": "friendly_name_API_KEY"
    },
    "friendly_name_AWS_IAM": {
      "ApiUrl": "https://xyz.us-west-2.amazonaws.com/graphql",
      "Region": "us-west-2",
      "AuthMode": "AWS_IAM",
      "ClientDatabasePrefix": "friendly_name_AWS_IAM"
    },
    "friendly_name_AMAZON_COGNITO_USER_POOLS": {
      "ApiUrl": "https://xyz.us-west-2.amazonaws.com/graphql",
      "Region": "us-west-2",
      "AuthMode": "AMAZON_COGNITO_USER_POOLS",
      "ClientDatabasePrefix": "friendly_name_AMAZON_COGNITO_USER_POOLS"
    },
    "friendly_name_OPENID_CONNECT": {
      "ApiUrl": "https://xyz.us-west-2.amazonaws.com/graphql",
      "Region": "us-west-2",
      "AuthMode": "OPENID_CONNECT",
      "ClientDatabasePrefix": "friendly_name_OPENID_CONNECT"
    }
  }
}

 

Android—Java

The useClientDatabasePrefix is added on the client builder, which signals to the builder that the ClientDatabasePrefix value should be used from the AWSConfiguration object (awsconfiguration.json).

AWSAppSyncClient client = AWSAppSyncClient.builder()
   .context(getApplicationContext())
   .awsConfiguration(new AWSConfiguration(getApplicationContext()))
   .useClientDatabasePrefix(true)
   .build();

iOS—Swift

The useClientDatabasePrefix is added to the AWSAppSyncCacheConfiguration, which reads the ClientDatabasePrefix value from the AWSAppSyncServiceConfig object (awsconfiguration.json).

let serviceConfig = try AWSAppSyncServiceConfig()
let cacheConfig = AWSAppSyncCacheConfiguration(useClientDatabasePrefix: true,
                                            appSyncServiceConfig: serviceConfig)
let clientConfig = AWSAppSyncClientConfiguration(appSyncServiceConfig: serviceConfig,
                                                   cacheConfiguration: cacheConfig)

let client = AWSAppSyncClient(appSyncConfig: clientConfig)

Public/private use case example

Here’s an example of how the newly introduced capabilities can be used in a client application.

Android—Java

The following code example creates a client factory to retrieve the client based on the need to operate in public (API_KEY) or private (AWS_IAM) authorization mode.

// AppSyncClientMode.java
public enum AppSyncClientMode {
    PUBLIC,
    PRIVATE
}

// ClientFactory.java
public class ClientFactory {

    private static Map<AWSAppSyncClient> CLIENTS;

    public static AWSAppSyncClient getAppSyncClient(AppSyncClientMode choice) {
        return CLIENTS[choice];
    }

    public static void initClients(final Context context) {
        AWSConfiguration awsConfigPublic = new AWSConfiguration(context);
        CLIENTS[PUBLIC] = AWSAppSyncClient.builder()
                                          .context(context)
                                          .awsConfiguration(awsConfigPublic)
                                          .useClientDatabasePrefix(true)
                                          .build();
        AWSConfiguration awsConfigPrivate = new AWSConfiguration(context);
        awsConfigPrivate.setConfiguration("friendly_name_AWS_IAM");
        CLIENTS[PRIVATE] = AWSAppSyncClient.builder()
                                           .context(context)
                                           .awsConfiguration(awsConfigPrivate)
                                           .useClientDatabasePrefix(true)
                                           .credentialsProvider(AWSMobileClient.getInstance())
                                           .build();
    }
}

This is what the usage would look like.

ClientFactory.getAppSyncClient(AppSyncClientMode.PRIVATE).query(fooQuery).enqueue(...);

iOS—Swift

The following code example creates a client factory to retrieve the client based on the need to operate in public (API_KEY) or private (AWS_IAM) authorization mode.

public enum AppSyncClientMode {
    case `public`
    case `private`
}

public class ClientFactory {
    static var clients: [AppSyncClientMode:AWSAppSyncClient] = [:]

    class func getAppSyncClient(mode: AppSyncClientMode) -> AWSAppSyncClient? {
        return clients[mode];
    }

    class func initClients() throws {
        let serviceConfigAPIKey = try AWSAppSyncServiceConfig()
        let cacheConfigAPIKey = try AWSAppSyncCacheConfiguration(useClientDatabasePrefix: true, appSyncServiceConfig: serviceConfigAPIKey)
        let clientConfigAPIKey = try AWSAppSyncClientConfiguration(appSyncServiceConfig: serviceConfigAPIKey, cacheConfiguration: cacheConfigAPIKey)
        clients[AppSyncClientMode.public] = try AWSAppSyncClient(appSyncConfig: clientConfigAPIKey)

        let serviceConfigIAM = try AWSAppSyncServiceConfig(forKey: "friendly_name_AWS_IAM")
        let cacheConfigIAM = try AWSAppSyncCacheConfiguration(useClientDatabasePrefix: true, appSyncServiceConfig: serviceConfigIAM)
        let clientConfigIAM = try AWSAppSyncClientConfiguration(appSyncServiceConfig: serviceConfigIAM,cacheConfiguration: cacheConfigIAM)
        clients[AppSyncClientMode.private] = try AWSAppSyncClient(appSyncConfig: clientConfigIAM)
    }
}

AWS Amplify & AWS AppSync JavaScript SDK

Amplify Client

The Amplify API.graphql method now accepts a new authMode parameter. If the authMode is not provided, Amplify will use the default auth mode that was configured:

// Creating a post is restricted to IAM 
const createdTodo = await API.graphql({
query: queries.createTodo,
variables: {input: todoDetails},
authMode: AUTH_MODE.AWS_IAM
});

To learn more, read the full documentation here.

AWS AppSync JavaScript SDK

In order to use multiple authorization types with the aws-appsync SDK, you can create multiple instances of the client where each instance uses a different authorization type.

Using different clients is supported in the following UI bindings for Apollo:

const client1 = new AWSAppSyncClient({
  url: awsConfig.aws_appsync_graphqlEndpoint,
  region: awsConfig.aws_appsync_region
  auth: { type: AUTH_TYPE.API_KEY, apiKey: awsConfig.aws_appsync_apiKey},
  offlineConfig: {
    keyPrefix: 'public'
  }
});

// Client 2 uses AWS_IAM as auth type, leverages Amplify's credentials handling/refresh
const client2 = new AWSAppSyncClient({
  url: awsConfig.aws_appsync_graphqlEndpoint,
  region: awsConfig.aws_appsync_region
  auth: { type: AUTH_TYPE.AWS_IAM, credentials: () => Auth.currentCredentials() },
  offlineConfig: {
    keyPrefix: 'private'
  }
});

To learn more, read the full documentation here.

Conclusion

In this post, we showed how you can use the new multiple authorization type setting in AWS AppSync to allow separate public and private data authorization in your GraphQL API. While your current authorization settings are the default on existing GraphQL APIs, you can add additional authorization types using the AWS AppSync console, AWS CLI, or AWS CloudFormation templates.