Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a AWS Cognito PreSignup Lambda in DotNet

Using a .Net Core 1.0 Lambda I want to be able to create a Lambda function which handles the PreSignUp trigger from an AWS Cognito User pool.

using Amazon.Lambda.Core;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

public class PreSignUp_SignUp
{
  public string userPoolId { get; set; }
  public const string EmailKey = "email";
  public const string PhoneNumber = "phone_number";
  public Dictionary<string,string> userAttributes { get; set; }
  public Dictionary<string, string> validationData { get; set; }
}

public class PreSignup_SignUpResponse
{
  public bool autoConfirmUser { get; set; }
}

public class Function
{
  public PreSignup_SignUpResponse FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
  {
      return new PreSignup_SignUpResponse { autoConfirmUser = true };
  }
}

Though the request succeeds and returns a response when invoking the Lambda with an example request of:

{
  "datasetName": "datasetName",
  "eventType": "SyncTrigger",
  "region": "us-east-1",
  "identityId": "identityId",
  "datasetRecords": {
    "SampleKey2": {
      "newValue": "newValue2",
      "oldValue": "oldValue2",
      "op": "replace"
    },
    "SampleKey1": {
      "newValue": "newValue1",
      "oldValue": "oldValue1",
      "op": "replace"
    }
  },
  "identityPoolId": "identityPoolId",
  "version": 2
}

When performing an actual SignUp via the .Net AmazonCognitoIdentityProviderClient I get back an error:

Amazon.CognitoIdentityProvider.Model.InvalidLambdaResponseException : Unrecognizable lambda output

Which I'm guessing means I have not got the shape of the response (and possibly even request) correct.

Does anyone have an example of a .Net Lambda function that works for the PreSignUp trigger in AWS Cognito?

like image 703
Bittercoder Avatar asked May 24 '17 00:05

Bittercoder


People also ask

How do I create a lambda function in AWS Cognito?

To add a user pool Lambda trigger with the console Go to the Amazon Cognito console , and then choose User Pools. Choose an existing user pool from the list, or create a user pool. Choose the User pool properties tab and locate Lambda triggers. Choose Add a Lambda trigger.

Does AWS Lambda support .NET framework?

NET Core. At the time of writing, . NET Core 3.1 is the latest . NET framework with native support with AWS Lambda.

Can I use C# in AWS Lambda?

AWS Lambda provides the following libraries for C# functions: Amazon. Lambda. Core – This library provides a static Lambda logger, serialization interfaces and a context object.


3 Answers

The cognito trigger requests/responses must contain the entire payload as specified in the Cognito trigger documentation:

http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html

I have found while diagnosing this issue the best place to start is by creating a function handler that takes a JObject and then logs and return's that same object e.g.

public JObject FunctionHandler(JObject input, ILambdaContext context)
{
    context.Logger.LogLine("Input was: " + input);
    return input;
}

This captures the payload in cloudwatch logs and then helps steer you towards the strongly typed structured required.

In my case for PreSignUp I ended up creating the following types to make a simple function which auto-verifies all supplied credentials.

public abstract class AbstractTriggerRequest
{
    [JsonProperty("userAttributes")]
    public Dictionary<string, string> UserAttributes { get; set; }
}

public abstract class AbstractTriggerResponse
{
}

public class TriggerCallerContext
{
    [JsonProperty("awsSdkVersion")]
    public string AwsSdkVersion { get; set; }
    [JsonProperty("clientId")]
    public string ClientId { get; set; }
}

public abstract class AbstractTriggerBase<TRequest, TResponse>
    where TRequest: AbstractTriggerRequest
    where TResponse: AbstractTriggerResponse
{
    [JsonProperty("version")]
    public int Version { get; set; }
    [JsonProperty("triggerSource")]
    public string TriggerSource { get; set; }
    [JsonProperty("region")]
    public string Region { get; set; }
    [JsonProperty("userPoolId")]
    public string UserPoolId { get; set; }  
    [JsonProperty("callerContext")]
    public TriggerCallerContext CallerContext { get; set; }
    [JsonProperty("request")]
    public TRequest Request { get; set; }
    [JsonProperty("response")]
    public TResponse Response { get; set; }
    [JsonProperty("userName", NullValueHandling = NullValueHandling.Ignore)]
    public string UserName { get; set; }
}

public class PreSignUpSignUpRequest : AbstractTriggerRequest
{
    [JsonProperty("validationData")]
    public Dictionary<string,string> ValidationData { get; set; }
}

The Lambda function then ends up with the following signature:

public class Function
{
    public PreSignUp_SignUp FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
    {
        context.Logger.LogLine("Auto-confirming everything!");

        input.Response = new PreSignUpSignUpResponse {
            AutoConfirmUser = true,
            // you can only auto-verify email or phone if it's present in the user attributes
            AutoVerifyEmail = input.Request.UserAttributes.ContainsKey("email"),
            AutoVerifyPhone = input.Request.UserAttributes.ContainsKey("phone_number") 
        };

        return input;
    }
}

Hopefully this helps anyone else running into issues writing Lambda triggers for Cognito.

like image 191
Bittercoder Avatar answered Oct 04 '22 11:10

Bittercoder


The previous two responses are now inaccurate unless you still use the old, less performant Amazon.Lambda.Serialization.Json.JsonSerializer. This old serializer uses Newtonsoft.Json while the new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer implements the recent System.Text.Json.

As a result, a JObject parameter is no longer appropriate and should instead be replaced with JsonElement. If you try to use JObject with this new serializer, you will get an error since the serializer doesn't know how to deal with this object.

You should read this to gain a better understanding of how it all works, but you access properties of the JsonElement using GetProperty("[insert property name here]").

For example:

public async Task<JsonElement> FunctionHandler(JsonElement input, ILambdaContext context)
{
    var request = input.GetProperty("request");
    var userAttributes = request.GetProperty("userAttributes");
    string email = userAttributes.GetProperty("email").GetString();

    return input;
}

This way, you don't need to construct entire classes to accommodate the required request and response parameters, just get and set the properties you need.

like image 24
DaddyProphet Avatar answered Oct 04 '22 11:10

DaddyProphet


There is already another great answer in here. However I'm not a expert .NET developer so this solution makes more sense to me.

class AutoVerifyEmail
{
    public AutoVerifyEmail() { }

    public JObject AutoVerifyEmailPreSignup(JObject input, ILambdaContext context)
    {
        //Console.Write(input); //Print Input

        input["response"]["autoVerifyEmail"] = true;
        input["response"]["autoConfirmUser"] = true;

        return input;
    }
}
like image 43
David Rees Avatar answered Oct 04 '22 12:10

David Rees