0
votes

I am trying to build an alexa skill that connects to an enterprise app that uses Azure AD authentication. We set everything up like in this article https://blogs.msdn.microsoft.com/premier_developer/2016/12/12/amazon-alexa-skills-development-with-azure-active-directory-and-asp-net-core-1-0-web-api/, but I am having problems with moving the token from the body of the request to the headers. The request comes through fine, I can parse it, I add the token to the header but on the test page in amazon I get this message: "There was an error calling the remote endpoint, which returned HTTP 302 : Found"

This is the code for adding the token to the headers:

 public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.Use(async (context, next) =>
        {
            string path = string.Format(ConfigurationManager.AppSettings["ExchangeTraceDrivePath"], "AlexaRequest", DateTime.Now.ToFileTime(), "");

            var stream = context.Request.Body;
            try
            {
                using (var buffer = new MemoryStream())
                {
                    await stream.CopyToAsync(buffer);
                    var bodyBuffer = new byte[buffer.Length];
                    buffer.Position = 0L;
                    buffer.Read(bodyBuffer, 0, bodyBuffer.Length);
                    var body = Encoding.UTF8.GetString(bodyBuffer);
                    using (var sw = new StreamWriter(path))
                    {
                        sw.WriteLine(DateTime.Now.ToString() + " body: " + body);
                        sw.WriteLine("---------------------------------------------------------------------------------------------");
                        foreach (var header in context.Request.Headers)
                        {
                            sw.WriteLine(DateTime.Now.ToString() + " header key: " + header.Key);
                            foreach (var val in header.Value)
                            {
                                sw.WriteLine(DateTime.Now.ToString() + " header value: " + val);
                            }
                        }
                        sw.WriteLine("---------------------------------------------------------------------------------------------");

                        dynamic json = JObject.Parse(body);
                        sw.WriteLine(DateTime.Now.ToString() + " parsed body: " + json);
                        sw.WriteLine("---------------------------------------------------------------------------------------------");

                        if (json?.session?.user?.accessToken != null)
                        {
                            sw.WriteLine(DateTime.Now.ToString() + " access accessToken found " +
                                         json?.session?.user?.accessToken);
                            sw.WriteLine("---------------------------------------------------------------------------------------------");

                            context.Request.Headers.Add("Authorization",
                                new string[] { string.Format("Bearer {0}", json?.session?.user?.accessToken) });
                            foreach (var header in context.Request.Headers)
                            {
                                sw.WriteLine(DateTime.Now.ToString() + " header key: " + header.Key);
                                foreach (var val in header.Value)
                                {
                                    sw.WriteLine(DateTime.Now.ToString() + " header value: " + val);
                                }
                            }
                            sw.WriteLine("---------------------------------------------------------------------------------------------");
                        }
                        buffer.Position = 0L;
                        context.Request.Body = buffer;
                    }
                }
            }
            catch
            {
            }
            finally
            {
                await next.Invoke();
                // Restore the original stream.
                context.Request.Body = stream;
            }

        });

        //ExpireTimeSpan and SlidinExpiration only work when  UseTokenLifetime = false
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            // This is NOT ASP.NET Session Timeout (that should be set to same value in web.config)
            // This is the expiration on the cookie that holds the Azure AD token
            ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToDouble(expirationTimeSpan)),

            // Set SlidingExpiration=true to instruct the middleware to re-issue a new cookie
            // with a new expiration time any time it processes a request which is more than
            // halfway through the expiration window.
            SlidingExpiration = true,

            Provider = new CookieAuthenticationProvider
            {
                // This method is called every time the cookie is authenticated, which
                // is every time a request is made to the web app
                OnValidateIdentity = CookieAuthNotification.OnValidateIdentity
            }
        });

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = authority,
                UseTokenLifetime = false,
                /*
                * Skipping the Home Realm Discovery Page in Azure AD
                * http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
                */
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = OpenIdConnectNotification.RedirectToIdentityProvider,
                    MessageReceived = OpenIdConnectNotification.MessageReceived,
                    SecurityTokenReceived = OpenIdConnectNotification.SecurityTokenReceived,
                    SecurityTokenValidated = OpenIdConnectNotification.SecurityTokenValidated,
                    AuthorizationCodeReceived = OpenIdConnectNotification.AuthorizationCodeReceived,
                    AuthenticationFailed = OpenIdConnectNotification.AuthenticationFailed
                },

            });
    }
1
Would you mind sharing the address that 302 redirection from and to? And did you config the Authentication/Authorization for the web API deployed on Azure?Fei Xue - MSFT
Which of the settings in that article is the url that does the 302 redirect? The web API is actually a MVC app in which I added a controller to service the alexa calls. The app was configured a couple of years ago and the same user that is linked in alexa can login perfectly on the web app.Oana Marina
Not able to identify the root cause without addition information. You may share the address redirect from and to see if any one can provide helpful suggestion.Fei Xue - MSFT

1 Answers

0
votes

I ended up creating a separate middleware to move the token from the body to the header

 public class AlexaJWTMiddleware : OwinMiddleware
{
    private readonly OwinMiddleware _next;

    public AlexaJWTMiddleware(OwinMiddleware next) : base(next)
    {
        _next = next;
    }


    public override Task Invoke(IOwinContext context)
    {
        var stream = context.Request.Body;
        if (context.Request.Headers.ContainsKey("SignatureCertChainUrl")
            && context.Request.Headers["SignatureCertChainUrl"]
                .Contains("https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem")
            && !context.Request.Headers.ContainsKey("Authorization"))
        {

            try
            {
                using (var buffer = new MemoryStream())
                {
                    stream.CopyToAsync(buffer);
                    var bodyBuffer = new byte[buffer.Length];
                    buffer.Position = 0L;
                    buffer.Read(bodyBuffer, 0, bodyBuffer.Length);
                    var body = Encoding.UTF8.GetString(bodyBuffer);

                    dynamic json = JObject.Parse(body);
                    if (json?.session?.user?.accessToken != null)
                    {

                        context.Request.Headers.Add("Authorization",
                            new string[] { string.Format("Bearer {0}", json?.session?.user?.accessToken) });
                    }
                    buffer.Position = 0L;
                    context.Request.Body = buffer;
                }
            }
            catch
            {
            }
            finally
            {
                // Restore the original stream.
                context.Request.Body = stream;
            }
        }
        else
        {
            return _next.Invoke(context);

        }
        return _next.Invoke(context);

    }
}

and then adding jwt authentication besides the openId one

  public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.Use(typeof(AlexaJWTMiddleware));
        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Tenant = domain,
                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidAudience = ConfigurationManager.AppSettings["ida:AppIdUri"]
                },
                AuthenticationType = "OAuth2Bearer",
            });

        //ExpireTimeSpan and SlidinExpiration only work when  UseTokenLifetime = false
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            // This is NOT ASP.NET Session Timeout (that should be set to same value in web.config)
            // This is the expiration on the cookie that holds the Azure AD token
            ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToDouble(expirationTimeSpan)),

            // Set SlidingExpiration=true to instruct the middleware to re-issue a new cookie
            // with a new expiration time any time it processes a request which is more than
            // halfway through the expiration window.
            SlidingExpiration = true,

            Provider = new CookieAuthenticationProvider
            {
                // This method is called every time the cookie is authenticated, which
                // is every time a request is made to the web app
                OnValidateIdentity = CookieAuthNotification.OnValidateIdentity
            }
        });

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = authority,
                UseTokenLifetime = false,
                /*
                * Skipping the Home Realm Discovery Page in Azure AD
                * http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
                */
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = OpenIdConnectNotification.RedirectToIdentityProvider,
                    MessageReceived = OpenIdConnectNotification.MessageReceived,
                    SecurityTokenReceived = OpenIdConnectNotification.SecurityTokenReceived,
                    SecurityTokenValidated = OpenIdConnectNotification.SecurityTokenValidated,
                    AuthorizationCodeReceived = OpenIdConnectNotification.AuthorizationCodeReceived,
                    AuthenticationFailed = OpenIdConnectNotification.AuthenticationFailed
                },

            });
    }