Swashbuckle Swagger UI padlocks with OAS3 [duplicate] - asp.net-core

This question already has answers here:
ASP Net Core 2.2 add locker icon only to methods that require authorization - Swagger UI
(3 answers)
Closed 2 years ago.
I have an ASP.Net Core Rest Web API documented with Swashbuckles's Swagger generation (.net v5 and Swashbuckle.AspNetCore v5.6.3). It generates Swagger documentation and UI with OAS3 support.
Also my API uses JWT bearer tokens. So, I added this code to the swagger configuration:
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
In = ParameterLocation.Header,
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Description = "Put `bearer` keyword in front of token"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme()
{
Reference = new OpenApiReference()
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
Array.Empty<string>()
}
});
And as expected, it added the authorization capability to the Swagger UI:
But I also noticed a few padlocks next to every HTTP request.
They are unlocked before authorization:
And after authorization they lock:
How could I get these padlocks to signal if authorization is required or not (I think I've seen the same padlocks somewhere doing this and it seems pretty natural to them to do this kind of thing as well)?
Already tried something like that, but it did not work (request headers no longer contained the jwt token):
options.OperationFilter<SecurityRequirementsOperationFilter>();
I figured out that the problem is that my Swagger is using OAS3 and SecurityRequirementsOperationFilter depending on OAS2. I've tried looking for alternatives, but it looks like there are no similar tools for OAS3.
What should I do?
Should I forget this feature? But that looks like the only purpose of these locks.
Are there any ways to have this feature and stay with OAS3 (also I not sure if I really need OAS3 support that much).

After some research, I found the answer here: https://stackoverflow.com/a/61365691/13851956.
So the code is:
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "No need to put the `bearer` keyword in front of token",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
options.OperationFilter<AuthorizationOperationFilter>();
public class AuthorizationOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var actionMetadata = context.ApiDescription.ActionDescriptor.EndpointMetadata;
var isAuthorized = actionMetadata.Any(metadataItem => metadataItem is AuthorizeAttribute);
var allowAnonymous = actionMetadata.Any(metadataItem => metadataItem is AllowAnonymousAttribute);
if (!isAuthorized || allowAnonymous)
{
return;
}
operation.Parameters ??= new List<OpenApiParameter>();
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
Array.Empty<string>()
}
}
};
}
}
The result is that padlocks appears only next to actions that a marked with [Authorize] and not marked with [AllowAnonymous] attributes:

Related

Enabling authentication in swagger

I created a asp.net core empty project running on .net6. I am coming across an issue when I am trying to enable authentication in swagger. Swagger UI runs as expected, the only issue is whenever I click on the Authorize green button on the swagger UI it will pop up but say Unknown Security definition type and give me two options Authorize and Close. It does not show under Available authorizations Bearer(http,Bearer) and allow me to enter a jwt value. I ran into this article that goes over bearer authentication but it isn't much help for me. Am I missing something in the .AddSwaggerGen()?
builder.Services.AddAuthorization();
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Name = "Authorization",
Description = "Bearer Authentication with JWT Token",
Type = SecuritySchemeType.Http
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
new List<string>()
}
});
});
var app = builder.Build();
app.UseAuthorization();
app.UseAuthentication();
......
I think Scheme should be "bearer" (lowercase b)
Scheme = "bearer",
You have used add authorization before defining your security definition.
First define security definition & then use add authorization

ASP.NET Core API: Authentication popup is not showing up in Swagger UI

I have an ASP.NET Core Web API with Swagger integrated using Swashbuckle. I have successfully integrated authorization on Swagger UI using an operation filter, because I do not want to show padlock for anonymous APIs.
.OperationFilter<AuthorizeFilter>()
Inside the filter, I have registered basic auth security requirement for Swagger UI.
My problem is, even though authentication is happening in APIs on Swagger UI, I no longer see that nice authentication popup which is giving when click on the padlock icon.
Could someone answer, why I am not seeing the auth popup now?
Assuming you have some endpoints that protected with [Authorize] attribute (can also be put on the controller).
[Route("")]
public class HelloController : ControllerBase
{
[Authorize]
[HttpGet("secure")]
public IActionResult GetSomethingPrivate()
{
return Ok("secret");
}
[HttpGet("public")]
public IActionResult GetSomethingPublic()
{
return Ok("hey");
}
}
You need to define a security scheme suitable for your needs. But do not require it globally, instead add it inside an operation filter. Here I've added a simple token auth:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ApiPlayground", Version = "v1" });
c.AddSecurityDefinition("token", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Name = HeaderNames.Authorization,
Scheme = "Bearer"
});
// dont add global security requirement
// c.AddSecurityRequirement(/*...*/);
c.OperationFilter<SecureEndpointAuthRequirementFilter>();
});
}
And here's the operation filter which references the token auth scheme we've just created. It checks if the endpoint needs authentication, then adds the requirement.
internal class SecureEndpointAuthRequirementFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (!context.ApiDescription
.ActionDescriptor
.EndpointMetadata
.OfType<AuthorizeAttribute>()
.Any())
{
return;
}
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "token" }
}] = new List<string>()
}
};
}
}
When you run the app, it works as you expect:
So does the auth popup:
Bonus: using basic auth
Define a new security scheme with following values:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSwaggerGen(c =>
{
// ...
// basic auth scheme (username + password)
c.AddSecurityDefinition("basic", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "basic"
});
// dont add global security requirement
// c.AddSecurityRequirement(/*...*/);
c.OperationFilter<SecureEndpointAuthRequirementFilter>();
});
}
Then update the operation filter to reference basic auth scheme:
internal class SecureEndpointAuthRequirementFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (!context.ApiDescription
.ActionDescriptor
.EndpointMetadata
.OfType<AuthorizeAttribute>()
.Any())
{
return;
}
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "basic" // <-- changed "token" -> "basic"
}
}] = new List<string>()
}
};
}
}
here's how the auth popup looks:
After logging in, requests include the correct Authorization header.
In my case, I am using JWT Token Authentication with .NET Core API. I Configure the swagger with the authorization token using the below code. This code will add global security requirements.
In Startup Class ConfigureServices Method.
//Swagger Configuration
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "API",
Version = "v2",
Description = "Your Api Description"
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
And In Configure Method
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
After running the API project Authorize button will appear on the right side. On Click the authorize button Authorization popup open and then pass the token in the text box with 'Bearer token'.
Authorization working fine for me.
the Abdusco's answer is true but modify the Apply method like this for AllowAnonymousAttribute Methods in Authorized Controllers
if (!context.ApiDescription
.ActionDescriptor
.EndpointMetadata
.OfType<AuthorizeAttribute>()
.Any() || context.ApiDescription
.ActionDescriptor
.EndpointMetadata
.OfType<AllowAnonymousAttribute>()
.Any())
{
return;
}

How to send custom headers with requests in Swagger UI?

I have some endpoints in the API - /user/login, /products.
In Swagger UI I post email and password to /user/login and as a response I receive a token string.
Then, I can copy the token from the response and want to use it as Authorization header value in requests to all urls if it's present, and to /products as an example.
Should I create a text input manually somewhere on the Swagger UI page, then put the token there and somehow inject in the requests or are there tools to manage it in a better way?
You can add a header parameter to your request, and Swagger-UI will show it as an editable text box:
swagger: "2.0"
info:
version: 1.0.0
title: TaxBlaster
host: taxblaster.com
basePath: /api
schemes:
- http
paths:
/taxFilings/{id}:
get:
parameters:
- name: id
in: path
description: ID of the requested TaxFiling
required: true
type: string
- name: auth
in: header
description: an authorization header
required: true
type: string
responses:
200:
description: Successful response, with a representation of the Tax Filing.
schema:
$ref: "#/definitions/TaxFilingObject"
404:
description: The requested tax filing was not found.
definitions:
TaxFilingObject:
type: object
description: An individual Tax Filing record.
properties:
filingID:
type: string
year:
type: string
period:
type: integer
currency:
type: string
taxpayer:
type: object
You can also add a security definition with type apiKey:
swagger: "2.0"
info:
version: 1.0.0
title: TaxBlaster
host: taxblaster.com
basePath: /api
schemes:
- http
securityDefinitions:
api_key:
type: apiKey
name: api_key
in: header
description: Requests should pass an api_key header.
security:
- api_key: []
paths:
/taxFilings/{id}:
get:
parameters:
- name: id
in: path
description: ID of the requested TaxFiling
required: true
type: string
responses:
200:
description: Successful response, with a representation of the Tax Filing.
schema:
$ref: "#/definitions/TaxFilingObject"
404:
description: The requested tax filing was not found.
definitions:
TaxFilingObject:
type: object
description: An individual Tax Filing record.
properties:
filingID:
type: string
year:
type: string
period:
type: integer
currency:
type: string
taxpayer:
type: object
The securityDefinitions object defines security schemes.
The security object (called "security requirements" in Swagger–OpenAPI), applies a security scheme to a given context. In our case, we're applying it to the entire API by declaring the security requirement a top level. We can optionally override it within individual path items and/or methods.
This would be the preferred way to specify your security scheme; and it replaces the header parameter from the first example. Unfortunately, Swagger-UI doesn't offer a text box to control this parameter, at least in my testing so far.
In ASP.NET Web API, the simplest way to pass-in a header on Swagger UI is to implement the Apply(...) method on the IOperationFilter interface.
Add this to your project:
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
operation.parameters.Add(new Parameter
{
name = "MyHeaderField",
#in = "header",
type = "string",
description = "My header field",
required = true
});
}
}
In SwaggerConfig.cs, register the filter from above using c.OperationFilter<T>():
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "YourProjectName");
c.IgnoreObsoleteActions();
c.UseFullTypeNameInSchemaIds();
c.DescribeAllEnumsAsStrings();
c.IncludeXmlComments(GetXmlCommentsPath());
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.OperationFilter<AddRequiredHeaderParameter>(); // Add this here
})
.EnableSwaggerUi(c =>
{
c.DocExpansion(DocExpansion.List);
});
}
In ASP.NET Core 2 Web API, using Swashbuckle.AspNetCore package 2.1.0, implement a IDocumentFilter:
SwaggerSecurityRequirementsDocumentFilter.cs
using System.Collections.Generic;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace api.infrastructure.filters
{
public class SwaggerSecurityRequirementsDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument document, DocumentFilterContext context)
{
document.Security = new List<IDictionary<string, IEnumerable<string>>>()
{
new Dictionary<string, IEnumerable<string>>()
{
{ "Bearer", new string[]{ } },
{ "Basic", new string[]{ } },
}
};
}
}
}
In Startup.cs, configure a security definition and register the custom filter:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
// c.SwaggerDoc(.....
c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
{
Description = "Authorization header using the Bearer scheme",
Name = "Authorization",
In = "header"
});
c.DocumentFilter<SwaggerSecurityRequirementsDocumentFilter>();
});
}
In Swagger UI, click on Authorize button and set value for token.
Result:
curl -X GET "http://localhost:5000/api/tenants" -H "accept: text/plain" -H "Authorization: Bearer ABCD123456"
Also it's possible to use attribute [FromHeader] for web methods parameters (or properties in a Model class) which should be sent in custom headers. Something like this:
[HttpGet]
public ActionResult Products([FromHeader(Name = "User-Identity")] string userIdentity)
At least it works fine for ASP.NET Core 2.1 and Swashbuckle.AspNetCore 2.5.0.
Here's a simpler answer for the ASP.NET Core Web Api/Swashbuckle combo, that doesn't require you to register any custom filters. Third time's a charm you know :).
Adding the code below to your Swagger config will cause the Authorize button to appear, allowing you to enter a bearer token to be sent for all requests. Don't forget to enter this token as Bearer <your token here> when asked.
Note that the code below will send the token for any and all requests and operations, which may or may not be what you want.
services.AddSwaggerGen(c =>
{
//...
c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "Bearer", new string[] { } }
});
//...
}
Via this thread.
This is how I achieved it in .NET 6
public class AddCustomHeaderParameter
: IOperationFilter
{
public void Apply(
OpenApiOperation operation,
OperationFilterContext context)
{
if (operation.Parameters is null)
{
operation.Parameters = new List<OpenApiParameter>();
}
operation.Parameters.Add(new OpenApiParameter
{
Name = "Custom Header",
In = ParameterLocation.Header,
Description = "Custom Header description",
Required = true,
});
}
}
And finally
services.AddSwaggerGen(c =>
{
c.OperationFilter<AddCustomHeaderParameter>();
});
For those who use NSwag and need a custom header:
app.UseSwaggerUi3(typeof(Startup).GetTypeInfo().Assembly, settings =>
{
settings.GeneratorSettings.IsAspNetCore = true;
settings.GeneratorSettings.OperationProcessors.Add(new OperationSecurityScopeProcessor("custom-auth"));
settings.GeneratorSettings.DocumentProcessors.Add(
new SecurityDefinitionAppender("custom-auth", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.ApiKey,
Name = "header-name",
Description = "header description",
In = SwaggerSecurityApiKeyLocation.Header
}));
});
}
Swagger UI will then include an Authorize button.
I ended up here because I was trying to conditionally add header parameters in Swagger UI, based on my own [Authentication] attribute I added to my API method. Following the hint that #Corcus listed in a comment, I was able to derive my solution, and hopefully it will help others.
Using Reflection, it's checking if the method nested down in apiDescription has the desired attribute (MyApiKeyAuthenticationAttribute, in my case). If it does, I can append my desired header parameters.
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) {
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
var attributes = ((System.Web.Http.Controllers.ReflectedHttpActionDescriptor)
((apiDescription.ActionDescriptor).ActionBinding.ActionDescriptor)).MethodInfo
.GetCustomAttributes(false);
if(attributes != null && attributes.Any()) {
if(attributes.Where(x => x.GetType()
== typeof(MyApiKeyAuthenticationAttribute)).Any()) {
operation.parameters.Add(new Parameter {
name = "MyApiKey",
#in = "header",
type = "string",
description = "My API Key",
required = true
});
operation.parameters.Add(new Parameter {
name = "EID",
#in = "header",
type = "string",
description = "Employee ID",
required = true
});
}
}
}
Update for OpenAPI 3, library Swashbuckle.AspNetCore. Correct code sample is provifded by this source: https://codeburst.io/api-security-in-swagger-f2afff82fb8e
For use with JWT Bearer correct code is:
services.AddSwaggerGen(c =>
{
// configure SwaggerDoc and others
// add JWT Authentication
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter JWT Bearer token **_only_**",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer", // must be lower case
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{securityScheme, new string[] { }}
});
}
I've seen one article with similar code for OpenAPI 2 and lost many hours because that example missed Reference definition. This resulted that Swashbuckle generated incorrect definitions and missed to include Authorizeation header. So check carefully the OpenAPI version you use.
Golang/go-swagger example: https://github.com/go-swagger/go-swagger/issues/1416
// swagger:parameters opid
type XRequestIdHeader struct {
// in: header
// required: true
XRequestId string `json:"X-Request-Id"`
}
...
// swagger:operation POST /endpoint/ opid
// Parameters:
// - $ref: #/parameters/XRequestIDHeader
If you are working with Nest.js, it can be achieved by adding addBearerAuth() while setting up the swagger(probably in the main.ts).
...........
const config = new DocumentBuilder()
.setTitle('Your title')
.setDescription('Your description')
.setVersion('1.0')
.addBearerAuth() // Add here
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
...........
With this added, we can pass in the Bearer token from the Swagger UI as:
PS: You have to use Authguard in respective controllers to protect your routes.
Just updating this for Asp.Net Core 6 web api and Swashbuckle.AspNetCore 6.4.0. It was surprisingly easy. In my case the header is required for all action methods so I have Required true.
One, create a Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter:
public class IMyCustomFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter()
{
Name= "MyCustomHeader",
In=ParameterLocation.Header,
Required=true
});
}
}
Two, tell Swagger to use it when you are configuring services:
builder.Services.AddSwaggerGen(config =>
{
//this causes Swagger to add an input so you can to add the value to header when you are executing an api method.
config.OperationFilter<IMyCustomFilter>();
});
Three, invoke Swagger as usual:
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
DISCLAIMER: this solution is not using Header.
If someone is looking for a lazy-lazy manner (also in WebApi), I'd suggest:
public YourResult Authorize([FromBody]BasicAuthCredentials credentials)
You are not getting from header, but at least you have an easy alternative.
You can always check the object for null and fallback to header mechanism.
In Feathers Swagger, to show the Authorize button on the top right in this case for BearerAuth, your config declaration must be done as such:
{
...config, // other config parameters
specs: {
info: {
title: 'My API',
description: 'My API Documentation',
version: '2.0',
},
components: {
securityDefinitions: {
BasicAuth: {
type: 'basic'
},
BearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT"
}
},
},
security: {
BearerAuth: []
},
},
}
Result

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook

Add id_token as claim AspNetCore OpenIdConnect middleware

I am trying to set IdTokenHint when sending the sign out request. In the previous Microsoft.Owin.Security.OpenIdConnect middleware I would be able to set the id_token as a claim in the SecurityTokenValidated method using the SecurityTokenValidated notification by doing something like this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
Notifications = new OpenIdConnectAuthenticationNotifications
{
//Perform claims transformation
SecurityTokenValidated = async notification =>
{
...
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
},
RedirectToIdentityProvider = async n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
}
With the new middleware Microsoft.AspNetCore.Authentication.OpenIdConnect (in ASP.NET Core RC2) I am having trouble trying to accomplish the same thing. I am assuming I should tap into the Events like so.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
Events = new OpenIdConnectEvents()
{
OnTokenValidated = context =>
{
...
context.SecurityToken.Payload.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
},
OnRedirectToIdentityProviderForSignOut = context =>
{
var idTokenHint = context.HttpContext.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
The problem I'm seeing is that the claims do not remain on the SecurityToken and don't get set on the HttpContext.User. What am I missing?
Regarding your code above, at least in version 2.1 of ASP.NET Core, the ID token can be accessed via context.Properties.GetTokenValue(...) (rather than as a user claim).
And, as Brock Allen said in a comment to your question, the OpenIdConnectHandler will automatically include the idTokenHint on sign out. However, and this bit me for a few hours today, when the handler processes the sign-in callback, it will only save the tokens for later if OpenIdConnectOptions.SaveTokens is set to true. The default is false, i.e., the tokens are no longer available when you do the sign-out.
So, if SaveTokens is true, the handler will automatically include the idTokenHint on logout, and you can also manually access the id token via context.Properties.GetTokenValue(...).