Not able to see swagger in IIS .Net Core - iis

I am currently developing Web Api .net core 5.0 with swagger.
I have hosted my application in IIS.I am able to see my Web APi working but my swagger is not responding.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestWebApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
Also in configure services I have used double dots as mentioned solutions in one of the forums.GitHub
app.UseSwaggerUI(c => c.SwaggerEndpoint("../swagger/v1/swagger.json", "TestWebApi v1")
);
I tried checking the web api as localapi but I am getting 404 error.
Thanks

app.UseSwaggerUI was in condition that isDevelopment loop. For this reason it was working in VS 2019 but not in IIS deployment.

Related

API returning 401 on Azure but not when run locally

The application in question is a web site which has an API tacked on the side that reuses the many of the data access methods developed for the website. So there maybe some interference between the web site authentication/authorization and the API's. But if that was the case I don't understand why it works locally.
When I run locally, I can test the API using Swagger or Postman to login, get the Bearer token and use it to call the API methods. On Azure, although the login succeeds the next call to the API returns a 401:
www-authenticate: Bearer error="invalid_token",error_description="The signature is invalid"
The most obvious difference is the appsettings.json which I've copied to the Application Configuration blade on Azure:
The original appsettings looked like:
"Rt5": {
"BaseAddress": "https://localhost:44357"
},
"Azure": {
"BuildNumber": ""
},
"AuthentificationJWT": {
"Audience": "ddt-ssp",
"Issuer": "ddt-rt5",
"SecurityKey": "not so secret"
},
Usage - ConfigureServices():
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/Home/Unauthorized";
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthentificationJWT:SecurityKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AuthentificationJWT:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AuthentificationJWT:Audience"],
ValidateLifetime = true,
ValidAlgorithms = new HashSet<string> { SecurityAlgorithms.HmacSha256 },
ValidTypes = new HashSet<string> { "JWT" }
};
});
services.AddAuthorization(o =>
{
o.AddPolicy(AllowViewExceptions.Policy, p
=> p.Requirements.Add(new AllowViewExceptions.Requirement()));
o.AddPolicy(AllowSubmitException.Policy, p
=> p.Requirements.Add(new AllowSubmitException.Requirement()));
});
Usage - Configure():
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
if (UseSwagger)
{
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); });
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Confession: this isn't my code and I'm sure why we have the Cookie stuff mentioned though is does seem to be offer an alternative authorization mechanism. See here:
public string GetAuthorization()
{
//TODO: use ExtractSecurityToken();
var claimToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName);
if (claimToken != null)
{
return $"Bearer {claimToken.Value}";
}
else if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var headerToken))
{
return headerToken;
}
return null;
}
private JwtSecurityToken ExtractSecurityToken()
{
var accessToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName)?.Value;
if (accessToken == null &&
_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var value))
{
accessToken = value.ToString().Replace("Bearer ", ""); // TODO: Find a better way then extracting Bearer e.g Get token without scheme
}
if (accessToken == null)
{
return null;
}
return (JwtSecurityToken)new JwtSecurityTokenHandler().ReadToken(accessToken);
}
The problem appears to be with Azure Websites' "Authentication / Authorization" option, which when enabled prevents the Web Api from accepting requests with the Authentication header. Disabling the option and using the Owin library with Azure AD provides the desired solution.
Every app service that is associated with Azure-AD has a corresponding Azure-AD application declaration of type Web app/API. This resource id is the "App ID URI" in the app service's Azure-AD application declaration.
To debug Authentication issues, please refer this msdn link.
It turned out the problem was caused by the bizarre architecture surround the application. The Access Token value (including the SHA256 checksum calculated using the security key) was being generated in another application and passed to this one to use for its communications.
When run localling both applications were using a security key stored in their appsettings.json file and were the same. The pipeline which deployed these applications to Azure was substituting security keys for random values and so they differed.
I've left the question here undeleted because Suryasri's link may help others later.

Call ASP .NET Core API in Azure from daemon application

I have limited experience on Azure AD and authentication mechanism. So far I cannot figure out why is not working. Here is the scenario:
I have an ASP net core 2.1 application deployed in azure web app service.
For authentication I’m using Open ID connect with .AddOpenIdConnect and provide client_id, secret_id, etc. When users are accessing my web API they are redirected to Microsoft login.
Now I need to expose an API to a third party application (scheduled web job) which is not in Azure.
I tried to use this sample from Microsoft, only the console app, as I already have the WebApp in Azure.
Running the sample I’m able to get the token, but when I call my API the response is the HTML to Microsoft login page.
On Azure portal on
Enterprise Application -> daemon-console -> Activity -> Service Principal sign-ins
I can see the success sign in.
Note: for testing I run the web app on my local machine and from the console application I’m calling API https://localhost:44306/api/test.
Asp .net core app:
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(option =>
{
option.Cookie.Name = "myWebApp";
option.Cookie.SecurePolicy = CookieSecurePolicy.Always;
option.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect(option =>
{
option.ClientId = client_id;
option.ClientSecret = client_secret;
option.Authority = authority;
option.SignedOutRedirectUri = "http://localhost:44306/";
option.CorrelationCookie.Name = "myWebAppCorrelation";
option.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
option.NonceCookie.Name = "WebAppNonce";
option.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
option.Resource = "https://graph.windows.net";
option.ResponseType = "id_token code";
})
Console app trying to access the API ( code extracted from Microsoft sample )
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); // ok
var httpClient = new HttpClient();
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(webApiUrl);
if (response.IsSuccessStatusCode) // ok
{
string json = await response.Content.ReadAsStringAsync(); // here I'm getting the HTML to login page
var result = JsonConvert.DeserializeObject<List<JObject>>(json);
Console.ForegroundColor = ConsoleColor.Gray;
processResult(result);
}
The only difference between the sample code and my scenario is that the web app from sample is using services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration)
but I cannot use .AddMicrosoftIdentityWebApi in Asp .Net core 2.1
Does anyone has an idea where the issue might be ? Do I need to add another authentication scheme ?
You need to support JWT authentication in addition to cookie authentication. So you need to add AddJwtBearer. You also need to extend the authentication check because you are now supporting multiple schemes.
This is how I would do it:
// public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, cfg => {
cfg.Authority = authority;
cfg.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = /*see scopes of your deamon app*/
};
})
.AddCookie(option =>
{
// ...
})
.AddOpenIdConnect(option =>
{
// ...
})
// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthentication();
// https://github.com/aspnet/Security/issues/1847
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (result.Succeeded)
{
context.User = result.Principal;
}
}
await next();
});
app.UseAuthorization();

Microsoft.Identity.Web and ASP.NET Core SignalR JWT authentication

I am using ASP.NET Core to make a web application that also uses SignalR Core to provide real time functionality. I use Azure AD B2C for user management. I have successfully used Microsoft.Identity.Web (https://github.com/AzureAD/microsoft-identity-web) to secure my API endpoints using tokens generated by Azure AD B2C.
I would like to do the same for my SignalR Core hubs. The documentation reads to add the appropriate annotation to your hubs/methods, which I have done. SignalR's client side library adds the access token as a query parameter which must be extracted and added to the context manually in the configuration of your ASP.NET core application, like so.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
However, this seems to be incompatible with the configuration supplied by Microsoft.Identity.Web, here:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));
How can I make SignalR work with Microsoft.Identity.Web?
That should do it:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
Func<MessageReceivedContext, Task> existingOnMessageReceivedHandler = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await existingOnMessageReceivedHandler(context);
StringValues accessToken = context.Request.Query["access_token"];
PathString path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = accessToken;
}
};
});
Instead of adding a JwtBearer, you can configure the JwtBearerOptions object this way.
Adapted from this documentation: https://github.com/AzureAD/microsoft-identity-web/wiki/customization
You can use Visual Studio to set up the SignalR connection, and then just add this line in startup.cs (VS might add it automatically)
services.AddSignalR().AddAzureSignalR();
This dev sample has SignalR set up, just the connection string is missing, but might give you an idea of what to do. Most of this was done automatically with VS. If you have issues setting it up, please open an issue in the repo. thanks.

Running asp.net core 2 app with OAuth2 as Azure Appservice results in 502 errors

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.

Enabling Azure AD Auth for Swagger UI hosted in Service Fabric

We enabled Swagger for Web API application which is hosted on Azure Service Fabric. We want to enable security on Swagger UI. So I followed below url which enables security –
https://blogs.msdn.microsoft.com/pratushb/2016/04/28/enable-swagger-to-authenticate-against-azure-ad/
https://github.com/domaindrivendev/Swashbuckle/issues/671 (response from Oleksandr-Tokmakov)
I could see the “Available authorizations” popup and I could see AAD authentication done successfully on another tab on click of Authorize button. But once authentication completed, I see the token not returns back to swagger ui and the authentication tab not closes.
Below is the code I used. (I created two AAD, one for Web Services hosted on Service Fabric and another for Swagger UI)
config.EnableSwagger(
c =>
{
c.SingleApiVersion("v1", "Title of Service");
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl("https://login.microsoftonline.com/tenentId-guid/oauth2/authorize")
.Scopes(scopes =>
{
scopes.Add("user_impersonation", "Access Services Local Swagger Secure");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
}
).EnableSwaggerUi(c =>
{
c.EnableOAuth2Support(
clientId: "Swagger AAD application Client Id",
clientSecret: "Swagger AAD application Key",
realm: "https://localhost:444/swagger/ui/o2c-html",
appName: "https://serviceslocal/swagger/", // Free text, no reference to AAD
scopeSeperator: "",
additionalQueryStringParams: new Dictionary<string, string>() { { "resource", "Web API AAD application Client Id" } }
);
}
);
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
// Correspond each "Authorize" role to an oauth2 scope
var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<AuthorizeAttribute>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
if (scopes.Any())
{
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", scopes }
};
operation.security.Add(oAuthRequirements);
}
}
}
I'm using Auth0, but here's what I got for OAuth2 that works for me.
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = Path.Combine(auth0Settings["Authority"].Value, "authorize")
});
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "docs";
c.InjectOnCompleteJavaScript("/swagger-ui.js");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Easy Streak API");
c.ConfigureOAuth2(auth0Settings["ClientId"].Value, auth0Settings["ClientSecret"].Value, auth0Settings["Authority"].Value, "EasyStreak API");
});

Resources