Authorization for JWT bearer in Swashbuckle .NET Core 2
Asked Answered
S

3

11

I use tokens generated by an authentication service for my app. No problems there. Now I have introduced Swashbuckle to document my API an I can authenticate as follows by sending the JWT with every request using this code;

services.AddSwaggerGen(c =>
{
    var a = new ApiKeyScheme();
    //c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
    //{ In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" });

    c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();

    c.SwaggerDoc("v2", new Info
    {
        Version = "v2",
        Title = "MyTitle",
        Description = "An interface for ...",
        TermsOfService = "None",
        Contact = new Contact() { Name = "MyApp", Email = "[email protected]", Url = "www.example.com" }
    });
    // Set the comments path for the Swagger JSON and UI.
    var basePath = AppContext.BaseDirectory;
    var xmlPath = Path.Combine(basePath, "cpDataCore.xml");
    c.IncludeXmlComments(xmlPath);
});

public class AuthorizationHeaderParameterOperationFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
        var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
        var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);

        if (isAuthorized && !allowAnonymous)
        {
            if (operation.Parameters == null)
                operation.Parameters = new List<IParameter>();

            operation.Parameters.Add(new NonBodyParameter
            {
                Name = "Authorization",
                In = "header",
                Description = "access token",
                Required = true,
                Type = "string"
            });
        }
    }
}

Which gives me the following header - as expected

accept:application/json
Accept-Encoding:gzip, deflate, br
Accept-Language:en-AU,en;q=0.9
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9naXZlbm5hbWUiOiJEZW5uaXMiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zdXJuYW1lIjoiR2FzY29pZ25lIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImRlbm5pc2ciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiI1NCIsIlJlZnJlc2hUb2tlbiI6IjY5OTA1NTFmLTNhOTQtNDVmYi1hYjc2LTZlOTQyNGE3NjJmOCIsIkFsbERhdGFSZWFkT25seUZvckFwcHJvdmVycyI6IlRydWUiLCJQcm9qZWN0SUQiOiI2IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoic3lzYWRtaW4iLCJuYmYiOjE1MTk2MzY2NDIsImV4cCI6MTUxOTYzODQ0MiwiaXNzIjoiaHR0cHM6Ly9kYXRhLmNpdmlscHJvc29mdHdhcmUuY29tLyIsImF1ZCI6Imh0dHBzOi8vcm1zLmNpdmlscHJvc29mdHdhcmUuY29tLyJ9.nBEZgzcmZVGhFJmKI8u7p7g7xPU13HEAGJu_lrWylnc
Connection:keep-alive
Cookie:username=demo; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9naXZlbm5hbWUiOiJUcm95IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvc3VybmFtZSI6IkVsZGVyIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6InRyb3kiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiI1IiwiUmVmcmVzaFRva2VuIjoiMTNhNzRmNDQtNmVmOC00MDQ3LTlmYWYtOWQ3MzI4MmNhZjQ4IiwiUHJvamVjdElEIjoiLTEiLCJuYmYiOjE1MDUwOTc3MjEsImV4cCI6MTUwNTA5ODYyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MDAwMC8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYwMDAwLyJ9.8You0XiUlvdHb2TRuDzaiXv6r74v7ga1Av_Z3ikmblU
Host:localhost:60000
Referer:http://localhost:60000/swagger/
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36

Although, I am not sure where the Cookie is coming from. That is nothing to do with my code. I just ignore it - so far so good.

The problem is that this means the token has to be entered with every request which is a pain. Ideally, I would want to authenticate using the inbuilt swagger interface - according to several articles, I should be able to do this;

c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
{ In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" });

This works fine, and I can add the token, there just seems to be a step I am missing to add the token to the header of every request. If I just add the auth, then this gives me the following header, which of course fails the authentication.

GET /api/ApprovalItemTypes HTTP/1.1
Host: localhost:60000
Connection: keep-alive
accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36
Referer: http://localhost:60000/swagger/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-AU,en;q=0.9
Cookie: username=demo; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW5_xxx__LTEiLCJuYmYiOjE1MDUwOTc3MjEsImV4cCI6MTUwNTA5ODYyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MDAwMC8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYwMDAwLyJ9.8You0XiUlvdHb2TRuDzaiXv6r74v7ga1Av_Z3ikmblU

What else do I need to do in order to get the request to include the token for every subsequent request?

Spew answered 26/2, 2018 at 9:33 Comment(2)
Can u share or mark solution?Gand
My solution for the issue per below.Spew
S
1

In the end I moved to NSwag so I am not sure what the original issue was. The solution in Nswag is as follows;

    services.AddSwaggerDocument(document =>
        {
            document.DocumentName = "CPSwagger";
            document.Title = "My Mobile API";
            document.Version = "v10.3.4";
            document.Description = "An interface for some software.";

            document.DocumentProcessors.Add(
                new SecurityDefinitionAppender("JWT token", new NSwag.OpenApiSecurityScheme
                {
                    Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
                    Name = "Authorization",
                    Description = "Copy 'Bearer ' + valid JWT token into field",
                    In = NSwag.OpenApiSecurityApiKeyLocation.Header
                }));
            document.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT token"));
        });
Spew answered 22/4, 2020 at 4:4 Comment(0)
R
1

Swagger would add the authorzation header if you specified the filter on your methods. If you globally require authorization my guess is that swagger doesn't recognize them.

You need to add a SecurityRequirement like this in your ConfigureServices:

c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>()
{
  { "Bearer", new string[]{ } }
});

This will require the header to be sent with every request if the token is set. If you didn't set the header before it'll not send it, but you'll still have the padlock sign next to your api description.

Rammish answered 19/3, 2018 at 17:22 Comment(2)
But, when add AddSecurityRequirement, it adds padlock in all the APi endpoints displayed in swagger UI even if that api endpoint does not have [Authorize] attribute decorated. How to fix this? Appreciate your help.Lorrianelorrie
@Lorrianelorrie the AddSecurityRequirement is for all the API endpoints. Swagger will add the padlock if you use the filter on your methods. Try to create your own question with code examples so we can help you further.Rammish
S
1

In the end I moved to NSwag so I am not sure what the original issue was. The solution in Nswag is as follows;

    services.AddSwaggerDocument(document =>
        {
            document.DocumentName = "CPSwagger";
            document.Title = "My Mobile API";
            document.Version = "v10.3.4";
            document.Description = "An interface for some software.";

            document.DocumentProcessors.Add(
                new SecurityDefinitionAppender("JWT token", new NSwag.OpenApiSecurityScheme
                {
                    Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
                    Name = "Authorization",
                    Description = "Copy 'Bearer ' + valid JWT token into field",
                    In = NSwag.OpenApiSecurityApiKeyLocation.Header
                }));
            document.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT token"));
        });
Spew answered 22/4, 2020 at 4:4 Comment(0)
B
0

If you define in code

c.AddSecurityDefinition("jwt", new ApiKeyScheme()
{ 
   In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" });

and then use it in not in .Parameters, but in .Security

operation.Security = new List<IDictionary<string, IEnumerable<string>>> {
            new Dictionary<string, IEnumerable<string>>
            {
                {"jwt", _scopes }
            }

then everything should work:

I do the same as you do, but you should add like follows (for oauth2 or jwt bearer token auth):

    public static class ServiceCollectionExtension
{
    private static string XmlCommentsFilePath
    {
        get
        {
            var basePath = PlatformServices.Default.Application.ApplicationBasePath;
            var fileName = Assembly.GetEntryAssembly().GetName().Name + ".xml";
            return Path.Combine(basePath, fileName);
        }
    }

    public static void AddMySwagger(
        this IServiceCollection services,
        ApiVersion defaultApiVersion,
        Func<ApiVersionDescription, Info> info,
        string authority = null,
        Dictionary<string, string> scopes = null)
    {
        services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");

        services.AddApiVersioning(o =>
        {
            o.ReportApiVersions = true;
            o.AssumeDefaultVersionWhenUnspecified = true;
            o.DefaultApiVersion = defaultApiVersion;
        });

        services.AddSwaggerGen(
            options =>
            {
                var provider = services.BuildServiceProvider()
                    .GetRequiredService<IApiVersionDescriptionProvider>();

                foreach (var description in provider.ApiVersionDescriptions)
                {
                    if (!description.IsDeprecated)
                        options.SwaggerDoc(description.GroupName, info(description));
                }

                options.OperationFilter<DefaultValues>();

                options.IncludeXmlComments(XmlCommentsFilePath);

                if (!string.IsNullOrEmpty(authority))
                {
                    options.AddSecurityDefinition("jwt", new ApiKeyScheme()
                    {
                        Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
                        Name = "Authorization",
                        In = "header",
                        Type = "apiKey"
                    });
                    //options.AddSecurityDefinition("oauth2", new OAuth2Scheme
                    //{
                    //  Flow = "implicit",
                    //  AuthorizationUrl = $"{authority}/connect/authorize",
                    //  Scopes = scopes ?? new Dictionary<string, string>()
                    //});
                    options.OperationFilter<AuthorizeCheckOperationFilter>(scopes?.Select(_ => _.Key).ToList() ?? new List<string>());
                }
            });
    }

    class AuthorizeCheckOperationFilter : IOperationFilter
    {
        private readonly IEnumerable<string> _scopes;

        public AuthorizeCheckOperationFilter(IEnumerable<string> scopes)
        {
            _scopes = scopes;
        }

        public void Apply(Operation operation, OperationFilterContext context)
        {
            var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
                               context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();

            if (hasAuthorize)
            {
                operation.Responses.Add("401", new Response { Description = "Unauthorized" });
                operation.Responses.Add("403", new Response { Description = "Forbidden" });

                operation.Security = new List<IDictionary<string, IEnumerable<string>>> {
                new Dictionary<string, IEnumerable<string>>
                {
                    //{"oauth2", _scopes},
                    {"jwt", _scopes }
                }
            };
            }
        }
    }
}

Usage:

        services.AddMySwagger(
            new ApiVersion(1, 0),
            __description => new Info { Title = $"API v{__description.ApiVersion}", Version = __description.ApiVersion.ToString() },
            Configuration.GetValue<string>("Authentication:Authority"),
            new Dictionary<string, string> { { Configuration.GetValue<string>("Authentication:Scope"), "Partnership API" } }
        );
Belia answered 14/2, 2019 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.