[PYTHON] Hit Cognito-authenticated AppSync from behind with IAM authentication

Hit Cognito-authenticated AppSync from behind with IAM authentication

Introduction

Previously, I posted a serverless web app hands-on article "Using AppSync on the front and back ends". Here, as a matter of course, "API Key" was used as the authentication mode. Authentication with this API Key is good for beginners to use it first or as a simple prototype, but considering the safety and the trouble of updating with a maximum expiration date of 365 days, Other authentication modes should be adopted if possible. ink (6).png You can see the key ... I will definitely forget to update the key after 365 days.

AppSync Authentication Mode Types

In addition to API Key, AppSync supports the following authentication modes.

** What is IAM ** It is a mechanism to control the range and access method of accessing AWS resources.

** What is Cognito User Pool ** It is a mechanism to provide application user management (sign-up and sign-in status).

** What is OpenID Connect ** I'm not familiar with it because I've never used it. (Like Cognito, it feels like logging in and authenticating using the issued token?)

Use a combination of multiple authentication modes

AppSync supported multiple authentication modes in May 2019. https://aws.amazon.com/jp/about-aws/whats-new/2019/05/aws-appsync-now-supports-configuring-multiple-authorization-type/

This makes it possible to authenticate with Cognito on the front end (client side) and IAM on the back end (server side).

Add IAM authentication and hit with Lambda (Python)

By using Amplify, it has become possible to quickly realize the construction of AppSync with Cognito authentication and the implementation of using it from the front end (web application).

From there in this article, Added IAM as an authentication provider for use on the backend, I would like to write about actually hitting (calling the API) from Lambda (Python).

Add IAM to additional authentication providers

AWS Console> AWS AppSync> Select the desired API> Settings There is an item called "Additional Authentication Provider" at the bottom, so press the New button. Screenshot 2020-03-01 at 10.02.59.png Select "AWS Identify and Access Management (IAM)" from the authentication mode combo box on the "Set additional authentication provider" pop-up screen and press the "Send" button. Screenshot 2020-03-01 at 10.04.31.png Confirm that "AWS Identify and Access Management (IAM)" is added to "Additional Authentication Provider" in the settings, and press the "Save" button at the bottom. (* Don't forget!) Screenshot 2020-03-01 at 10.05.41.png

Specify the authentication mode in the schema

When using multiple authentication modes, it is necessary to add to the schema. You can specify in detail what you can do in which authentication mode for each root type such as Query, Mutation, Subscription, and user-defined function fields and object types in it. (* Cannot be specified for input type)

By default, nothing is specified, which means that only the primary authentication mode is allowed, and the added authentication mode is not allowed. You can specify the authentication mode by making the following markings.

See this developer guide for more details. (Rather, everything is written here) https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/appsync-dg%20.pdf

AWS Console> AWS AppSync> Select the desired API> Schema After editing the graphql schema here, click the "Save Schema" button. Screenshot 2020-03-01 at 14.19.30.png

Here are some examples of schemas before editing (Before.graphql) and after editing (After.graphql).

Before.graphql


type Mutation {
	createSampleAppsyncTable(input: CreateSampleAppsyncTableInput!): SampleAppsyncTable
	updateSampleAppsyncTable(input: UpdateSampleAppsyncTableInput!): SampleAppsyncTable
	deleteSampleAppsyncTable(input: DeleteSampleAppsyncTableInput!): SampleAppsyncTable
}

type Query {
	getSampleAppsyncTable(group: String!, path: String!): SampleAppsyncTable
	listSampleAppsyncTables(filter: TableSampleAppsyncTableFilterInput, limit: Int, nextToken: String): SampleAppsyncTableConnection
}

type SampleAppsyncTable {
	group: String!
	path: String!
}

After.graphql


type Mutation {
	createSampleAppsyncTable(input: CreateSampleAppsyncTableInput!): SampleAppsyncTable
		@aws_cognito_user_pools @aws_iam
	updateSampleAppsyncTable(input: UpdateSampleAppsyncTableInput!): SampleAppsyncTable
		@aws_iam
	deleteSampleAppsyncTable(input: DeleteSampleAppsyncTableInput!): SampleAppsyncTable
}

type Query @aws_cognito_user_pools {
	getSampleAppsyncTable(group: String!, path: String!): SampleAppsyncTable
	listSampleAppsyncTables(filter: TableSampleAppsyncTableFilterInput, limit: Int, nextToken: String): SampleAppsyncTableConnection
}

type SampleAppsyncTable @aws_cognito_user_pools @aws_iam {
	group: String!
	path: String!
}

In this example, it is specified as follows. Mutation.create: Cognito and IAM Mutation.update: IAM only Mutation.delete: Cognito only (not specified = primary) Query: get and list are Cognito only

As I thought so far, it seems good to design the primary authentication to be IAM instead of Cognito and give Cognito permission only for what is needed on the client side.

Hit with Lambda (Python)

It is an implementation for hitting AppSync with IAM authentication permission with Python of Lambda.

sample_graphql_with_iam.py


import requests
from requests_aws4auth import AWS4Auth

AWS_REGION = os.environ["AWS_REGION"]
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]
AWS_SESSION_TOKEN = os.environ["AWS_SESSION_TOKEN"]
ENDPOINT = "https://{0}.{1}.{2}.amazonaws.com/{3}".format("xxxxxxxxxxxxxxxxxxxxxx", "appsync-api", AWS_REGION, "graphql")
AUTH = AWS4Auth(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, 'appsync', session_token=AWS_SESSION_TOKEN)

def apiCreateTable(group, path):
    try:        
        body_json = {"query": 
            """
            mutation create {{
                createSampleAppsyncTable(input:{{
                group: \"{0}\"
                path: \"{1}\"
              }}){{
                group path
              }}
            }}
            """.format(group, path)
        }
        body = json.dumps(body_json)
        response = requests.request("POST", ENDPOINT, auth=AUTH, data=body, headers={})
    except Exception as e:
        logger.exception(e)
        raise e

Add policy to IAM role

Add the AppSync policy to the Lambda IAM role you want to run. All you have to do is attach AWSAppSyncAdministrator. (* I think it's better to write an inline policy to allow only the target API, not the entire AppSync, but I don't know how to write it. Can anyone please tell me!)

Afterword

You can hit it from the front, and you can hit it from the back. If you wait in front of you to be hit from behind, it will feel cool. (* Subscription story) Both front and back are boring with Cognito, so I'm using IAM behind. (* Authentication story)

As a result of talking about JAWS UG Hamamatsu (Uncle Amplify), I came up with the idea of writing this article.

In LT at the end of last year, I used this kind of illustration. Screenshot 2020-03-01 at 16.55.05.png (Image of waiting in front of being hit from behind)

I would like to continue to capture AppSync, which seems to be useful for various purposes and is worth developing!

Recommended Posts

Hit Cognito-authenticated AppSync from behind with IAM authentication
Try IAM Database Authentication from Python
Passwordless authentication with RDS and IAM (Python)
Hit the Twitter API after Oauth authentication with Django