3

Integrating swagger and Firebase is really hard because Google token request and response is not standard. So I used the password flow and added a middleware in .NET Core 3.1 Web API to authenticate swagger 5.x. I hope it helps.

The first step is configuring your API to use Firebase for token validation. So we need to add this code to the startup:

services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            })
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.Authority = authority; // this is a url like this "https://securetoken.google.com/{firebase project Id}"
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = authority, //url again
                    ValidateAudience = true,
                    ValidAudience = projectId, // {Firebase Project ID}
                    ValidateLifetime = true
                };
                options.RequireHttpsMetadata = false;
            });

and you need to middleware for Authentication and Authorization in your startup as well.

 app.UseAuthentication();
 app.UseAuthorization();

Here we go. Now if you use the [Authorize] attribute on your controller methods and send bearer token in the header it will validate the token by Firebase and works fine. Now I want to show how you can configure your Swagger to provide the token automatically.

At first, you need to implement a middleware post method to convert Standard Password flow to Google password flow and also convert the result. I did it

[ApiController]
[Route("v1/[controller]")]
public class AuthController : Controller
{
    [HttpPost]
    public async Task<ActionResult> GetToken([FromForm]LoginInfo loginInfo)
    {
        string uri = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={your project api key in firebase}";
        using (HttpClient client = new HttpClient())
        {
            FireBaseLoginInfo fireBaseLoginInfo = new FireBaseLoginInfo
            {
                Email = loginInfo.Username,
                Password = loginInfo.Password
            };
            var result = await client.PostAsJsonAsync<FireBaseLoginInfo, GoogleToken>(uri, fireBaseLoginInfo);
            Token token = new Token
            {
                token_type = "Bearer",
                access_token = result.idToken,
                id_token = result.idToken,
                expires_in = int.Parse(result.expiresIn),
                refresh_token = result.refreshToken

            };
            return Ok(token);
        }
    }
}

public class LoginInfo
{
    public string Username { get; set; }
    public string Password { get; set; }

}

public class FireBaseLoginInfo
{
    public string Email { get; set; }
    public string Password { get; set; }
    public bool ReturnSecureToken { get; set; } = true;
}

public class GoogleToken
{
    public string kind { get; set; }
    public string localId { get; set; }
    public string email { get; set; }
    public string displayName { get; set; }
    public string idToken { get; set; }
    public bool registered { get; set; }
    public string refreshToken { get; set; }
    public string expiresIn { get; set; }
}


public class Token
{
    internal string refresh_token;

    public string token_type { get; set; }
    public int expires_in { get; set; }
    public int ext_expires_in { get; set; }
    public string access_token { get; set; }
    public string id_token { get; set; }
}

You know we don't have PostAsJsonAsync in .NET Core, so I add an extension for it.

public static class HttpExtensions
{
    public static async Task<R> PostAsJsonAsync<T, R>(
        this HttpClient httpClient, string url, T data)
    {
        var response = await httpClient.PostAsJsonAsync<T>(url, data);
        if(response.IsSuccessStatusCode)
        {
            return await response.Content.ReadAsJsonAsync<R>();
        }
        else
        {
            return default(R);
        }
       

    }
    public static Task<HttpResponseMessage> PostAsJsonAsync<T>(
        this HttpClient httpClient, string url, T data)
    {
        var options = new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() },
            Formatting = Formatting.Indented
        };

        var dataAsString = JsonConvert.SerializeObject(data, options);
        var content = new StringContent(dataAsString);
        content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        return httpClient.PostAsync(url, content);
    }

    public static async Task<T> ReadAsJsonAsync<T>(this HttpContent content)
    {
        var dataAsString = await content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(dataAsString);
    }
}

Now swagger configuration.

services
            .AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My Api", Version = "v1" });
                
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    
                    Type = SecuritySchemeType.OAuth2,
                    
                    Flows = new OpenApiOAuthFlows
                    {
                        Password = new OpenApiOAuthFlow
                        {
                            TokenUrl = new Uri("/v1/auth",UriKind.Relative),
                            Extensions = new Dictionary<string, IOpenApiExtension>
                                {
                                    { "returnSecureToken", new OpenApiBoolean(true) },
                                },

                        }

                    }
                });
                c.OperationFilter<AuthorizeCheckOperationFilter>();
            })

AuthorizeCheckOperationFilter filter is a custom filter

public class AuthorizeCheckOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {


        var requiredScopes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
                 .OfType<AuthorizeAttribute>()
                 .Select(attr => attr.Policy)
                 .Distinct();

        if (requiredScopes.Any())
        {

            var oAuthScheme = new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
            };

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

        }
    }
}

and finally you know that you need to add swagger middleware

 app.UseSwagger()
          .UseSwaggerUI(c =>
          {
              c.SwaggerEndpoint($"/swagger/v1/swagger.json", "Chivado Api");
          });

and now you can use it very simple here is a snapshot enter image description here

jps
  • 20,041
  • 15
  • 75
  • 79
Farshid
  • 161
  • 1
  • 8
  • Have any idea how to do something similar in django? Thanks for the post. – Zach Aug 26 '20 at 02:22
  • Thanks for this. Swagger works well like this with firebase authentication. Extra info: When you authenticate in swagger, you don't need to set the client id and secret. – szab.kel Mar 04 '21 at 07:27

0 Answers0