timheuer / alexa-skills-dotnet

An Amazon Alexa Skills SDK for .NET
MIT License
547 stars 108 forks source link

Amazon requires skill validates request is from Amazon, how to access httpRequest object to view cert? #230

Closed hobbykitjr closed 2 years ago

hobbykitjr commented 3 years ago

Sorry, I don't see where/how to get the httpRequest object to validate the skill request?

the project starts w/ SkillRequest which has all the HTTP header stripped already. How do you get the HttpRequest object to validate the request so the skill can be published (Amazon is rejecting my skill using this project because of this)

all requests to your api must validate they came from amazon and are recent if you want to publish it. Thanks,

github-actions[bot] commented 3 years ago

👋 Hi there! Thanks for your contribution to the project by posting your first issue!

hobbykitjr commented 3 years ago

I hope this helps someone else.

First in your startup.cs enable buffering in your config. This is for .net core 3.1, if you have something else, try here: https://stackoverflow.com/questions/40494913/how-to-read-request-body-in-an-asp-net-core-webapi-controller

            app.Use((context, next) =>
            {
                context.Request.EnableBuffering();
                return next();
            });

Then you'll need a verification method like so:

  private static async Task<bool> ValidateRequest(HttpRequest request, SkillRequest skillRequest)
        {
            request.Headers.TryGetValue("SignatureCertChainUrl", out var signatureChainUrl);
            if (string.IsNullOrWhiteSpace(signatureChainUrl))
            {
                Trace.TraceError("Validation failed. Empty SignatureCertChainUrl header");
                return false;
            }

            Uri certUrl;
            try
            {
                certUrl = new Uri(signatureChainUrl);
            }
            catch
            {
                Trace.TraceError($"Validation failed. SignatureChainUrl not valid: {signatureChainUrl}");
                return false;
            }

            request.Headers.TryGetValue("Signature", out var signature);
            if (string.IsNullOrWhiteSpace(signature))
            {
                Trace.TraceError("Validation failed - Empty Signature header");
                return false;
            }

            request.Body.Seek(0, SeekOrigin.Begin);
            string body;
            using (StreamReader stream = new StreamReader(request.Body))
            {
                body = stream.ReadToEnd();
            }

            if (string.IsNullOrWhiteSpace(body))
            {
                Trace.TraceError("Validation failed - the JSON is empty");
                return false;
            }

            bool isTimestampValid = RequestVerification.RequestTimestampWithinTolerance(skillRequest);
            bool valid = await RequestVerification.Verify(signature, certUrl, body);

            if (!valid || !isTimestampValid)
            {
                Trace.TraceError("Validation failed - RequestVerification failed");
                return false;
            }
            else
            {
                return true;
            }
        }

Finally, at the start of your handle request. Get the HttpRequest out of the HttpContext

        [HttpPost]
        public ActionResult<SkillResponse> HandleRequest([FromBody] SkillRequest skillRequest) {
            var request = HttpContext.Request;

            bool isValid = AlexaController.ValidateRequest(request, skillRequest).Result;
            if (!isValid)
            {
                return new BadRequestResult();
            }
....

the validation method will rewind the body stream and check the whole thing for the cert.

timheuer commented 3 years ago

Related discussion #184

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.