36

I'm creating a REST api in ASP.NET Core 1.0. I was using Swagger to test but now I added JWT authorization for some routes. (with UseJwtBearerAuthentication)

Is it possible to modify the header of the Swagger requests so the routes with the [Authorize] attribute can be tested?

MonkeyDreamzzz
  • 3,978
  • 1
  • 39
  • 36
  • Possible duplicate of [Setting up Swagger (ASP.NET Core) using the Authorization headers (Bearer)](https://stackoverflow.com/questions/43447688/setting-up-swagger-asp-net-core-using-the-authorization-headers-bearer) – Michael Freidgeim Apr 01 '19 at 10:11
  • @MichaelFreidgeim This question was asked before the linked question so seems like the linked question is the duplicate – MonkeyDreamzzz Apr 03 '19 at 11:02
  • "Possible duplicate" is a way to clean-up - to close similar questions and keep one with the best answers. The date is not essential. See http://meta.stackexchange.com/questions/147643/should-i-vote-to-close-a-duplicate-question-even-though-its-much-newer-and-ha If you agree that it requires clarification please vote on http://meta.stackexchange.com/questions/281980/add-clarification-link-to-possible-duplicate-automated-comment – Michael Freidgeim Apr 03 '19 at 15:16
  • 3
    @MichaelFreidgeim I agree cleanup is needed. The solution on this page worked for me and that is why I marked an answer as accepted. On the other page the OP didn't bother to check if this question was already asked and also didn't mark an answer as accepted so I don't see how that should become the main question and this page the duplicate. One of the answers on the other page even refers to an answer here. This page contains all possible answers, the other one does not so please mark the other one as duplicate. – MonkeyDreamzzz Apr 04 '19 at 08:33

7 Answers7

37

I struggled with the same problem and found a working solution in this blogpost: http://blog.sluijsveld.com/28/01/2016/CustomSwaggerUIField

It comes down to adding this in your configurationoptions

services.ConfigureSwaggerGen(options =>
{
   options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
});

and the code for the operationfilter

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"
         });
      }
   }
}

Then you will see an extra Authorization TextBox in your swagger where you can add your token in the format 'Bearer {jwttoken}' and you should be authorized in your swagger requests.

HansVG
  • 749
  • 6
  • 10
  • 5
    where do you obtain the bearer token to put into the jwttoken field when using the try out functionality in swagger ui? – Azure Terraformer Oct 08 '17 at 20:13
  • 3
    Just a quick helper; using Microsoft.AspNetCore.Mvc.Authorization; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System.Collections.Generic; using System.Linq; – statler Feb 26 '18 at 08:54
  • 3
    How can we automatically assign the Bearer in the authorization field? – hubert17 Apr 05 '18 at 09:14
  • yes, this works but is cumbersome. Like @hubert17 I would like to add the token to the request automatically. This is working in a 4.5.2 api, but not under core 2.0. – Papa Stahl Jun 22 '18 at 15:22
  • If you're stuggling, `NonBodyParameter` and `IParameter` may be replaced with `OpenApiParameter` – Mateja Petrovic Nov 02 '20 at 00:21
21

Currently Swagger has functionality for authentication with JWT-token and can automatically add token into header (I'm using Swashbuckle.AspNetCore 1.1.0).

enter image description here

The following code should help achieve this.

In the Startup.ConfigureServices():

services.AddSwaggerGen(c =>
{
    // Your custom configuration
    c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
    c.DescribeAllEnumsAsStrings();
    // JWT-token authentication by password
    c.AddSecurityDefinition("oauth2", new OAuth2Scheme
    {
        Type = "oauth2",
        Flow = "password",
        TokenUrl = Path.Combine(HostingEnvironment.WebRootPath, "/token"),
        // Optional scopes
        //Scopes = new Dictionary<string, string>
        //{
        //    { "api-name", "my api" },
        //}
    });
});

Check and configure TokenUrl if your endpoint is different.

In the Startup.Configure():

app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");

    // Provide client ID, client secret, realm and application name (if need)

    // Swashbuckle.AspNetCore 4.0.1
    c.OAuthClientId("swagger-ui");
    c.OAuthClientSecret("swagger-ui-secret");
    c.OAuthRealm("swagger-ui-realm");
    c.OAuthAppName("Swagger UI");

    // Swashbuckle.AspNetCore 1.1.0
    // c.ConfigureOAuth2("swagger-ui", "swagger-ui-secret", "swagger-ui-realm", "Swagger UI");
});

If your endpoint for authentication by token follows the OAuth2 standard, all should work. But just in case, I have added sample of this endpoint:

public class AccountController : Controller
{
    [ProducesResponseType(typeof(AccessTokens), (int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.Unauthorized)]
    [HttpPost("/token")]
    public async Task<IActionResult> Token([FromForm] LoginModel loginModel)
    {
        switch (loginModel.grant_type)
        {
            case "password":
                var accessTokens = // Authentication logic
                if (accessTokens == null)
                    return BadRequest("Invalid user name or password.");
                return new ObjectResult(accessTokens);

            case "refresh_token":
                var accessTokens = // Refresh token logic
                if (accessTokens == null)
                    return Unauthorized();
                return new ObjectResult(accessTokens);

            default:
                return BadRequest("Unsupported grant type");
        }
    }
}

public class LoginModel
{
    [Required]
    public string grant_type { get; set; }

    public string username { get; set; }
    public string password { get; set; }
    public string refresh_token { get; set; }
    // Optional
    //public string scope { get; set; }
}

public class AccessTokens
{
    public string access_token { get; set; }
    public string refresh_token { get; set; }
    public string token_type { get; set; }
    public int expires_in { get; set; }
}
Pavel K.
  • 983
  • 9
  • 21
  • This works, except when UserId/Password/Client/Secret failed, it just failed quietly in the background and still show logged in. Any thought? – Whoever Dec 28 '17 at 04:16
  • Please check that you return HTTP status code 400, if authorization failed. It's requirement from RFC 6749 and Swagger also process it. I have updated the answer. – Pavel K. Dec 28 '17 at 14:34
  • Yes, I use IdentityServer 4 and it returns 400. But swagger UI shows Logout button as if user has successfully logged in. I'm not sure how to config that swagger popup screen to show authentication failed. – Whoever Dec 28 '17 at 20:16
  • I'm afraid, but I haven't any new ideas. As you can see I have decided to create my own implementation of authentication method (as I will more free with DB wrappers and schema). The Swagger works correctly with my token-endpoint with exception a part of refresh the token, which seems does not implemented yet. – Pavel K. Dec 29 '17 at 08:17
  • 1
    I use `Swashbuckle.AspNetCore 4.0.1` package in my ASP.NET Core application. It doesn't recognize `ConfigureOAuth2()` method. Do I miss something? Compile error: 'SwaggerUIOptions' does not contain a definition for 'ConfigureOAuth2' and no accessible extension method 'ConfigureOAuth2' accepting a first argument of type 'SwaggerUIOptions' could be found (are you missing a using directive or an assembly reference?) – Tohid Jan 02 '19 at 14:05
  • 2
    @Tohid please check updated answer, in the Swashbuckle.AspNetCore 4.0.1 was a bit change in the API. – Pavel K. Jan 08 '19 at 13:43
  • @PavelK. I'm also using Swashbuckle.AspNetCore 4.0.1. In the authorize dialog, is there a way to hide clientId & clientSecret? I have flow = "password", but the dialog shows Username / Password AND clientId / clientSecret. If I set to "application" then it only shows clientId / clientSecret. – SledgeHammer Mar 26 '19 at 19:43
  • @SledgeHammer, I'm afraid, but I don't know how to hide these fields. As I understand, they are part of "password" flow. About screenshot - this is from Swashbuckle.AspNetCore 1.1.0 (currently I'm also see these fields). – Pavel K. Mar 28 '19 at 09:03
6

To expand on HansVG answer which worked for me (thanks) and since I don't have enough contribution points I can't answer emseetea question directly. Once you have the Authorization textbox you will need to call the endpoint that generate the token which will be outside your must [Authorize] area of endpoints.

Once you have called that endpoint to generate the token from the endpoint you can copy it out of the results for that endpoint. Then you have the token to use in your other areas that are must [Authorize]. Just paste it in the textbox. Make sure, as HansVG mentioned, to add it in the correct format, which needs to include "bearer ". Format = "bearer {token}".

Timm Hagen
  • 123
  • 1
  • 2
  • 5
6

Thanks to the Pavel K.'s answer, this is the way I finally resolved this issue in ASP.NET Core 2.2 with Swagger 4.0.1.

In the Startup.cs ConfigureServices():

public void ConfigureServices(IServiceCollection services)
{
    .
    .
    .
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "...", Version = "v1" });
        .
        .
        .
        c.AddSecurityDefinition("Bearer", new OAuth2Scheme
        {
            Flow = "password",
            TokenUrl = "/token"
        });

       // It must be here so the Swagger UI works correctly (Swashbuckle.AspNetCore.SwaggerUI, Version=4.0.1.0)
       c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
       {
           {"Bearer", new string[] { }}
       });
    });
    .
    .
    .
}

In the Startup.cs Configure():

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    .
    .
    .
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "...");
        // Provide client ID, client secret, realm and application name (if need)
        c.OAuthClientId("...");
        c.OAuthClientSecret("...");
        c.OAuthRealm("...");
        c.OAuthAppName("...");
    });
    .
    .
    .
}

And here is how I made an endpoint to give out a JWT token:

[ApiController, Route("[controller]")]
public class TokenController : ControllerBase
{
    [HttpPost, AllowAnonymous]
    public async Task<ActionResult<AccessTokensResponse>> RequestToken([FromForm]LoginRequest request)
    {
        var claims = await ValidateCredentialAndGenerateClaims(request);

        var now = DateTime.UtcNow;
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_setting.SecurityKey));
        var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _setting.Issuer,
            audience: _setting.Audience,
            claims: claims,
            notBefore: now,
            expires: now.AddMinutes(_setting.ValidDurationInMinute),
            signingCredentials: signingCredentials);

        return Ok(new AccessTokensResponse(token));
    }
}

All your rules and logic on validating user name and password (and/or client_id and clinet_secret) will be in ValidateCredentialAndGenerateClaims().

If you just wonder, these are my request and response models:

/// <summary>
/// Encapsulates fields for login request.
/// </summary>
/// <remarks>
/// See: https://www.oauth.com/oauth2-servers/access-tokens/
/// </remarks>
public class LoginRequest
{
    [Required]
    public string grant_type { get; set; }
    public string username { get; set; }
    public string password { get; set; }
    public string refresh_token { get; set; }
    public string scope { get; set; }

    public string client_id { get; set; }
    public string client_secret { get; set; }
}

/// <summary>
/// JWT successful response.
/// </summary>
/// <remarks>
/// See: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
/// </remarks>
public class AccessTokensResponse
{
    /// <summary>
    /// Initializes a new instance of <seealso cref="AccessTokensResponse"/>.
    /// </summary>
    /// <param name="securityToken"></param>
    public AccessTokensResponse(JwtSecurityToken securityToken)
    {
        access_token = new JwtSecurityTokenHandler().WriteToken(securityToken);
        token_type = "Bearer";
        expires_in = Math.Truncate((securityToken.ValidTo - DateTime.UtcNow).TotalSeconds);
    }

    public string access_token { get; set; }
    public string refresh_token { get; set; }
    public string token_type { get; set; }
    public double expires_in { get; set; }
}
Tohid
  • 6,175
  • 7
  • 51
  • 80
5

You may add any additional header with API call by using this swagger configuration

// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new Info
    {
        Version = "v1",
        Title = "Core API",
        Description = "ASP.NET Core API",
        TermsOfService = "None",
        Contact = new Contact
        {
            Name = "Raj Kumar",
            Email = ""
        },
        License = new License
        {
            Name = "Demo"
        }
    });
    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[]{}}
    });
});

enter image description here

Raj kumar
  • 1,275
  • 15
  • 20
0

I would also check for AuthorizeAttribute.

var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;

var hasAuthorizedFilter = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
var hasAuthorizedAttribute = context.MethodInfo.ReflectedType?.CustomAttributes.First().AttributeType ==
                                     typeof(AuthorizeAttribute);

if ((hasAuthorizedFilter || hasAuthorizedAttribute) && !allowAnonymous)
{
    var oAuthScheme = new OpenApiSecurityScheme
    {
        Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
    };

    operation.Security = new List<OpenApiSecurityRequirement>
    {
        new OpenApiSecurityRequirement
        {
            [ oAuthScheme ] = new List<string>()
        }
    };
}

Controller Action:

[Authorize(Policy = AppConfiguration.PermissionReadWrite)]
[Route("api/[controller]")]
[ApiController]
public class FooController : ControllerBase
{
   ...
}
Sudip Shrestha
  • 441
  • 4
  • 12
0

I integrate swagger with firebase Configure Swagger Authentication with Firebase (google) in .Net core enter image description here

Farshid
  • 161
  • 1
  • 8