Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authentication with Google in Web API and Swagger

I have integrated a Web API c# app with Swagger. I have already implemented AAD authentication which is working fine. Now I want to add Google authentication. I have done code as below.

SwaggerConfig.cs

 c.OAuth2("oauth2")
       .Description("Google Auth")
       .Flow("implicit")
       .AuthorizationUrl($"https://accounts.google.com/o/oauth2/v2/auth")
       .Scopes(scopes => scopes.Add("openid", "Sign you in permission"));

Startup.cs

app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
googleAuthOptions = new GoogleOAuth2AuthenticationOptions()
{
            ClientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
            ClientSecret = "xxxxxxxxxxxxxxxxxxxx",

};
app.UseGoogleAuthentication(googleAuthOptions);

I get authorized from google login screen and successfully redirected back to the UI. But when I try to access any method, I get 401 Unauthorized error even bearer token is there.
After authenticating with google

I know I haven't verified the access token in the Startup.cs file because I have lack of knowledge. I have gone through few articles with complex implementation but I want to achieve this in a simplest way. Below code works for AAD without any further code verification.

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
                new Microsoft.Owin.Security.ActiveDirectory.WindowsAzureActiveDirectoryBearerAuthenticationOptions
                {
                    Tenant = Settings.AzureADTenant,
                    TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    {
                        ValidAudiences = new string[] { Settings.AzureADAudience, Settings.AzureAppURI },
                        ValidIssuers = new string[] {
                            $"https://sts.windows.net/{Settings.AzureADTenant}/",
                            $"https://login.microsoftonline.com/{Settings.AzureADTenant}/v2.0"
                        }
                    }
                });
like image 611
Zahid Hamid Avatar asked Jan 20 '26 05:01

Zahid Hamid


1 Answers

The token you're getting is an access token, the problem is that swagger by default makes the token request with the response_type parameter as token instead of token id_token.

You could authenticate with google from swagger following the below steps:

  1. In your wwwroot folder (if you don't have one, you can create it), create a JS file with the following content

    window.swaggerUiAuth = window.swaggerUiAuth || {};
    window.swaggerUiAuth.tokenName = 'id_token';
    if (!window.isOpenReplaced) {
        window.open = function (open) {
            return function (url) {
                url = url.replace('response_type=token', 'response_type=token id_token');
                return open.call(window, url);
            };
        }(window.open);
        window.isOpenReplaced = true;
    }
    
  2. Once that's done, in your Configure method add the following:

    app.UseSwaggerUI(c =>
    {
        //your additional stuff...
        c.OAuthAdditionalQueryStringParams(new Dictionary<string, string> {{ "nonce", "anyNonceStringHere" }});
        c.OAuthClientId(this.oauth2Config.ClientId);
        c.InjectJavascript("YourJustCreatedJSFileName.js");
    });
    
  3. Now in your AddSwaggerGen builder, add the security definition and the security requirements. (You can split it in methods just to make it cleaner).

    //in your ConfigureServices 
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "wellship_svc_app", Version = "v1" });
        this.AddSwaggerOAuth2Configuration(c);
    }); 
    

Create this private method in your startup class. Note the extensions property in the security scheme using the id_token instead of the access token.

private void AddSwaggerOAuth2Configuration(SwaggerGenOptions swaggerGenOptions) 
{
    
    var securityScheme = new OpenApiSecurityScheme
    {
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows()
        {
            Implicit = new OpenApiOAuthFlow()
            {
                AuthorizationUrl = new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
                Scopes = new Dictionary<string, string> {{"email", "email"}, {"profile", "profile"}}
            }
        },
        Extensions = new Dictionary<string, IOpenApiExtension>
        {
            {"x-tokenName", new OpenApiString("id_token")}
        },
    };
        
    swaggerGenOptions.AddSecurityDefinition("Bearer", securityScheme) ;

    var securityRequirements = new OpenApiSecurityRequirement 
    {
        {
            new OpenApiSecurityScheme 
            { 
                Reference = new OpenApiReference 
                { 
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer" 
                } 
            },
            new List<string> {"email", "profile"}
        } 
    };

    swaggerGenOptions.AddSecurityRequirement(securityRequirements);
}
  1. Create a google token validator class:
public class GoogleTokenValidator : ISecurityTokenValidator
{
    private readonly JwtSecurityTokenHandler _tokenHandler;

    public GoogleTokenValidator()
    {
        _tokenHandler = new JwtSecurityTokenHandler();
    }

    public bool CanValidateToken => true;

    public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

    public bool CanReadToken(string securityToken)
    {
        return _tokenHandler.CanReadToken(securityToken);
    }

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        validatedToken = null;
        var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result;

        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, payload.Name),
            new (ClaimTypes.Name, payload.Name),
            new (JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
            new (JwtRegisteredClaimNames.GivenName, payload.GivenName),
            new (JwtRegisteredClaimNames.Email, payload.Email),
            new (JwtRegisteredClaimNames.Sub, payload.Subject),
            new (JwtRegisteredClaimNames.Iss, payload.Issuer)
        };

        try
        {
            var principle = new ClaimsPrincipal();
            principle.AddIdentity(new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme));
            return principle;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;

        }
    }
}
  1. Validate your token with your custom google validator class:
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 

})
.AddJwtBearer(o =>
{
    o.IncludeErrorDetails = true;
    o.SecurityTokenValidators.Clear();
    o.SecurityTokenValidators.Add(new GoogleTokenValidator());
});
like image 50
Anthony Serrano Bianco Avatar answered Jan 21 '26 18:01

Anthony Serrano Bianco



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!