I've developed a chatbot using botframework and it just works fine and deployed in azure also fine.
Then I added KeyVault service and published again but this time I'm getting 502 error and in appInsights this log:
Microsoft.Bot.Schema.BotException:
at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.Base.ChannelConnector, Version=3.2.3.7, Culture=neutral, PublicKeyToken=null)
Inner exception System.Exception handled at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext:
at Microsoft.Bot.ChannelConnector.BotAPI.ThrowOnFailedStatusCode (Microsoft.Bot.Base.ChannelConnector, Version=3.2.3.7, Culture=neutral, PublicKeyToken=null)
at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.Base.ChannelConnector, Version=3.2.3.7, Culture=neutral, PublicKeyToken=null)
When I've removed(commented part) the code for implementing the KeyVault service, it worked again:
public static IWebHost CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, builder) =>
{
// var keyVaultEndpoint = GetKeyVaultEndpoint();
// if (!string.IsNullOrEmpty(keyVaultEndpoint))
// {
// var azureServiceTokenProvider = new AzureServiceTokenProvider();
// var keyVaultClient = new KeyVaultClient(new VaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
// builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
// }
}
).UseStartup<Startup>()
.Build();
private static string GetKeyVaultEndpoint() => "https://XXX.vault.azure.net/";
These are the steps and the code from Docs, which I've implemented
This is typically because the bot times out. The bot needs to respond within 15 seconds to the direct line channel. You can do further debugging/logging/testing to see if this is the scenario in this case. You can also use Ngrok to debug locally to see if needed.
Related
I have a asp.net core 3.1 web api which adds messages to Azure Queue. In this case I am using an account to login into the VS2019 and debug the code in my local development environment. The same account is also added to the access policy for the storage account with the role : Storage Queue Data Contributor
Here I am trying to remove the dependency of using connectionstring and queue name to connect to the Azure Queue service from the asp.net core web api. All works fine in the case where I am providing connectionstring and queue name. But when I am trying to go with the route of Managed Service Identity in context to my local development environment it is throwing error.
Here goes the code for the asp.net core web api:
TestAPIController.cs:
[HttpPost]
public async Task Post([FromBody]WeatherForecast data)
{
var message = JsonSerializer.Serialize(data);
await _queueClient.SendMessageAsync(message, null, TimeSpan.FromSeconds(-1));
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//services.AddHostedService<WeatherDataService>();
services.AddAzureClients(builder =>
{
builder.AddClient<QueueClient, QueueClientOptions>((options, _, _) =>
{
options.MessageEncoding = QueueMessageEncoding.Base64;
var credential = new DefaultAzureCredential();
var queueUri = new Uri("<AzureQueueURL>");
return new QueueClient(queueUri, credential, options);
});
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo{Title = "queue_storage", Version = "v1"});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "queue_storage v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
On executing the above code I found the below error:
Azure.Identity.AuthenticationFailedException: Azure CLI authentication failed due to an unknown error. ERROR: The command failed with an unexpected error. Here is the traceback:
ERROR: Get Token request returned http error: 400 and server response: {"error":"invalid_grant","error_description":"AADSTS700082: The refresh token has expired due to inactivity.áThe token was issued on 2021-04-23T15:29:05.0816332Z and was inactive for 90.00:00:00.\r\nTrace ID: cbd16614-192a-409b-82a8-348597e81900\r\nCorrelation ID: 85b72955-22a3-4b1c-b05c-d7054ce6a6c6\r\nTimestamp: 2022-05-08 11:22:40Z","error_codes":[700082],"timestamp":"2022-05-08 11:22:40Z","trace_id":"cbd16614-192a-409b-82a8-348597e81900","correlation_id":"85b72955-22a3-4b1c-b05c-d7054ce6a6c6","error_uri":"https://login.microsoftonline.com/error?code=700082"}
I referred to the this article :https://www.rahulpnath.com/blog/getting-started-with-azure-queue-storage/ for my POC.
Can anyone provide their guidance to fix this issue
My goal is to:
In scheduled functions - add message to SignalR
In SPA application (vue.js) subscribe to the event and call API to update the view
For now I'm trying to get anything to/from SignalR in my Function app (isolated, .net 6.0).
What I have in a function app:
[Function("negotiate")]
public HttpResponseData Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
[SignalRConnectionInfoInput(HubName = "AdminHub", ConnectionStringSetting = "AzureSignalRConnectionString")] SignalRConnectionInfo connectionInfo)
{
_logger.LogInformation($"SignalR Connection URL = '{connectionInfo.Url}'");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
response.WriteString($"Connection URL = '{connectionInfo.Url}'");
return response;
}
}
[Function("SendMessage")]
[SignalROutput(HubName = "AdminHub", ConnectionStringSetting = "AzureSignalRConnectionString")]
public SignalRMessage SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "get")] Microsoft.Azure.Functions.Worker.Http.HttpRequestData req)
{
return
new SignalRMessage
{
Target = "cancelToHandle",
MethodName = "cancelToHandle",
Arguments = new[] { "hello" }
};
}
[Function("SignalRTest")]
public static async Task SignalRTest([SignalRTrigger("AdminHub", "messages", "cancelToHandle", ConnectionStringSetting = "AzureSignalRConnectionString")] string message, ILogger logger)
{
logger.LogInformation($"Receive {message}.");
}
Negotiate function is not called. When should it be called?
If I call SendMessage, no error, but nothing happens in SignalR service. Should I see connections and messages there? (zero in the Metrics for now).
I've tried to create a test "emulator" client - just a console application:
var url = "http://<azureSignalRUrl>/AdminHub";
var connection = new HubConnectionBuilder()
.WithUrl(url)
.WithAutomaticReconnect()
.Build();
// receive a message from the hub
connection.On<string, string>("cancelToHandle", (user, message) => OnReceiveMessage(user, message));
await connection.StartAsync();
// send a message to the hub
await connection.InvokeAsync("SendMessage", "ConsoleApp", "Message from the console app");
void OnReceiveMessage(string user, string message)
{
Console.WriteLine($"{user}: {message}");
}
and it throws the exception ": 'A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (:80)'
I think I'm missing overall understanding of what is supposed to happen:
when should negotiate function be triggered
can I see messages that I send in Azure portal (in SignalR service)?
how can I easily receive them in testing purposes
what do parameters/properties mean (target / method name / category). Example:
SignalRTriggerAttribute has the following constructor
public SignalRTriggerAttribute(string hubName, string category, string #event, params string[] parameterNames);
and Output binding receives any custom model I create?
which settings should be set in SignalR service - for now all I set it Serverless mode + CORS
Here are the few link which will help in using SignalIr service extension in functions
Using SignalIr service extension
https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/Extensions/SignalR/SignalRFunction.cs
Below is the sample link for triggering negotiate function
https://github.com/Log234/azure-functions-signalr-dotnet-isolated-demo/blob/main/SignalRDemo/NegotiationFunctions.cs
for complete understanding of SignalIR here is the Github and MS document.
The Azure Web generates an HTTP Error 502.3 - Code 0x80072f7, the specified CGI application encountered an error and the server terminated the process.
Regular ASP.NET Core code
Use of Dependency Injection inside the code
no error during the build on an Azure Pipeline
2 pages displaying basic .cshtml text work
2 pages calling External web services generate the HTTP Error 502.3
The WebApp works perfectly on my local machine when running the command dotnet run
502 - Web server received an invalid response while acting as a
gateway or proxy server.
There is a problem with the page you are
looking for, and it cannot be displayed. When the Web server (while
acting as a gateway or proxy) contacted the upstream content server,
it received an invalid response from the content server.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry();
services.AddDbContext<Context>(opt =>
opt.UseInMemoryDatabase("ConfigurationList"));
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "Gateway API",
Version = "v1",
Description = "CRUD",
TermsOfService = "None",
Contact = new Contact
{
Name = ",
Email = "#alt-f1.be",
Url = "https://twitter.com/abdelkrim"
},
License = new License
{
Name = "(c) Copyright 2019, all rights reserved.",
Url = "http://www.alt-f1.be"
}
});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddSingleton<IAPI, API>();
services.AddSingleton<IAPIMandate, APIMandate>();
services.AddSingleton<IApiRules, ApiRules>();
services.AddSingleton<IApiTransactions, ApiTransactions>();
Console.WriteLine("scoped api");
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Some environment variables were missing and a little piece of code was executed that crashed the WebApp.
if (Environment.GetEnvironmentVariable("a-token") == null)
{
...
Environment.Exit(-1);
}
that was the reason logs where unavailable
I created a simple ASP.NET Core Web application using OAuth authentication from Google. I have this running on my local machine fine.
Yet after deploying this as an AppService to Azure the OAuth redirects seem to get messed up.
The app itself can be found here:
https://gcalworkshiftui20180322114905.azurewebsites.net/
Here's an url that actually returns a result and shows that the app is running:
https://gcalworkshiftui20180322114905.azurewebsites.net/Account/Login?ReturnUrl=%2F
Sometimes the app responds fine but once I try to login using Google it keeps loading forever and eventually comes back with the following message:
The specified CGI application encountered an error and the server terminated the process.
Behind the scenes, the authentication callback that seems to be failing with a 502.3 error:
502.3 Bad Gateway “The operation timed out”
The error trace can be found here:
https://gcalworkshiftui20180322114905.azurewebsites.net/errorlog.xml
The documentation from Microsoft hasn't really helped yet.
https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-overview
Further investigation leads me to believe that this has to do with the following code:
public GCalService(string clientId, string secret)
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/calendar-dotnet-quickstart.json");
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = clientId,
ClientSecret = secret
},
new[] {CalendarService.Scope.Calendar},
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
// Create Google Calendar API service.
_service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "gcalworkshift"
});
}
As I can imagine Azure not supporting personal folders? Googling about this doesn't tell me much.
I followed Facebook, Google, and external provider authentication in ASP.NET Core and Google external login setup in ASP.NET Core to create a ASP.NET Core Web Application with Google authentication to check this issue.
I also followed .NET console application to access the Google Calendar API and Calendar.ASP.NET.MVC5 to build my sample project. Here is the core code, you could refer to them:
Startup.cs
public class Startup
{
public readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder); //C:\Users\{username}\AppData\Roaming\Google.Apis.Auth
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = "{ClientId}";
googleOptions.ClientSecret = "{ClientSecret}";
googleOptions.Scope.Add(CalendarService.Scope.CalendarReadonly); //"https://www.googleapis.com/auth/calendar.readonly"
googleOptions.AccessType = "offline"; //request a refresh_token
googleOptions.Events = new OAuthEvents()
{
OnCreatingTicket = async (context) =>
{
var userEmail = context.Identity.FindFirst(ClaimTypes.Email).Value;
var tokenResponse = new TokenResponse()
{
AccessToken = context.AccessToken,
RefreshToken = context.RefreshToken,
ExpiresInSeconds = (long)context.ExpiresIn.Value.TotalSeconds,
IssuedUtc = DateTime.UtcNow
};
await dataStore.StoreAsync(userEmail, tokenResponse);
}
};
});
services.AddMvc();
}
}
}
CalendarController.cs
[Authorize]
public class CalendarController : Controller
{
private readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
private async Task<UserCredential> GetCredentialForApiAsync()
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{ClientId}",
ClientSecret = "{ClientSecret}",
},
Scopes = new[] {
"openid",
"email",
CalendarService.Scope.CalendarReadonly
}
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
string userEmail = ((ClaimsIdentity)HttpContext.User.Identity).FindFirst(ClaimTypes.Name).Value;
var token = await dataStore.GetAsync<TokenResponse>(userEmail);
return new UserCredential(flow, userEmail, token);
}
// GET: /Calendar/ListCalendars
public async Task<ActionResult> ListCalendars()
{
const int MaxEventsPerCalendar = 20;
const int MaxEventsOverall = 50;
var credential = await GetCredentialForApiAsync();
var initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ASP.NET Core Google Calendar Sample",
};
var service = new CalendarService(initializer);
// Fetch the list of calendars.
var calendars = await service.CalendarList.List().ExecuteAsync();
return Json(calendars.Items);
}
}
Before deploying to Azure web app, I changed the folder parameter for constructing the FileDataStore to D:\home, but got the following error:
UnauthorizedAccessException: Access to the path 'D:\home\Google.Apis.Auth.OAuth2.Responses.TokenResponse-{user-identifier}' is denied.
Then, I tried to set the parameter folder to D:\home\site and redeploy my web application and found it could work as expected and the logged user crendentials would be saved under the D:\home\site of your azure web app server.
Azure Web Apps run in a secure environment called the sandbox which has some limitations, details you could follow Azure Web App sandbox.
Additionally, you mentioned about the App Service Authentication which provides build-in authentication without adding any code in your code. Since you have wrote the code in your web application for authentication, you do not need to set up the App Service Authentication.
For using App Service Authentication, you could follow here for configuration, then your NetCore backend can obtain additional user details (access_token,refresh_token,etc.) through an HTTP GET on the /.auth/me endpoint, details you could follow this similar issue. After retrieved the token response for the logged user, you could manually construct the UserCredential, then build the CalendarService.
I created a basic project using Visual Studio 2015 Update 3 for Web API (nothing custom, bare bone) and deployed it to Azure (Free Account) following the instruction here.
Then I created a Console client with the following code.
public static async Task<bool> ReadValues()
{
try
{
// Authenticate the user and get a token from Azure AD
//AuthenticationResult authResult = await AuthContext.AcquireTokenSilentAsync(Resource, ClientId);
AuthenticationResult authResult = AuthContext.AcquireToken(Resource, ClientId, RedirectUri);
// Create an HTTP client and add the token to the Authorization header
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
//"Bearer"
authResult.AccessTokenType
, authResult.AccessToken);
// Call the Web API to get the values
var requestUri = new Uri(WebApiUri, "api/values");
Console.WriteLine("Reading values from '{0}'.", requestUri);
HttpResponseMessage httpResponse = await httpClient.GetAsync(requestUri);
Console.WriteLine("HTTP Status Code: '{0}'", httpResponse.StatusCode.ToString());
//Console.WriteLine("HTTP Header: '{0}'", httpClient.DefaultRequestHeaders.Authorization.ToString());
if (httpResponse.IsSuccessStatusCode)
{
//
// Code to do something with the data returned goes here.
//
var s = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(s);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(httpResponse.ReasonPhrase);
}
return (httpResponse.IsSuccessStatusCode);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
It works fine when I run the WEB API locally from the Visual Studio in debug, but when I deploy it to the Azure, it returns Unauthorized.
Few common things that I might get asked:
I do receive a valid bearer token
I have created the App registrations in the Azure AD for bot hthe WEB API and the client
The client and WEB API are using the correct redirect, resource uri
The account I am using to login is the same as the one used to create the Azure account and it has full privileges in the domain/AD/API
On the API side, this is whole of the startup.auth.cs
using System.Configuration;
using System.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security.ActiveDirectory;
using Owin;
using WebApi;
[assembly: OwinStartup("default", typeof(Startup))]
namespace WebApi
{
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
}
}
What else should I check?
Other references
https://www.simple-talk.com/cloud/security-and-compliance/azure-active-directory-part-3-developing-native-client-applications/
Thanks for help from Juunas who provided me with a working copy, I was able to narrow down the cause. When I attached a debugger to the Azure instance of the Web API I was able to see a exception for Bad Audience. On trying to retrace my steps, I found that while deployment from Visual Studio, I was selection Enterprise Authentication in settings that was causing the web.config to change in way that lead to the problem. Not selecting that option, I was able to access the API through bearer token.