0

I am attempting to authenticate a google jwt bearer token from my .net core webapi application and continually receive 401s. I have verified that the token is valid via jwt.io. I am trying to implement the solution offered here, google-jwt-authentication-with-aspnet-core-2-0

Can anyone see what is wrong with my code?

Below is my code:

Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        // Configure SnapshotCollector from application settings
        services.Configure<SnapshotCollectorConfiguration>(Configuration.GetSection(nameof(SnapshotCollectorConfiguration)));

        // Add SnapshotCollector telemetry processor.
        services.AddSingleton<ITelemetryProcessorFactory>(sp => new SnapshotCollectorTelemetryProcessorFactory(sp));

        conString = Microsoft
        .Extensions
        .Configuration
        .ConfigurationExtensions
        .GetConnectionString(this.Configuration, "DefaultConnection");

        services.AddDbContext<GotNextDBContext>(
            options =>
            options.UseSqlServer(conString));



        services.AddTransient<ILocationService, LocationService>();
        services.AddTransient<ICompanyService, CompanyService>();
        services.AddTransient<IUserLocationLogService, UserLocationLogService>();
        services.AddTransient<IUserService, UserService>();
        services.AddTransient<ILanguageService, LanguageService>();
        services.AddTransient<IGenderService, GenderService>();
        services.AddTransient<ISportService, SportService>();
        services.AddTransient<IMeasurementService, MeasurementService>();


        var clientIds = new List<string>();
        clientIds.Add("[myClientId]");
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

        })
        .AddJwtBearer(o =>
        {

            o.SecurityTokenValidators.Clear();
            o.SecurityTokenValidators.Add(new GoogleTokenValidator(clientIds: clientIds ));
        });

        services.AddRouting();
        services.AddAutoMapper();

        services.AddAntiforgery(options =>
        {

            options.Cookie.Name = "X-CSRF-TOKEN-GOTNEXT-COOKIE";                
            options.HeaderName = "X-CSRF-TOKEN-GOTNEXT-HEADER";
            options.SuppressXFrameOptionsHeader = false;
        });

        var serviceProvider = services.BuildServiceProvider();

        var context = serviceProvider.GetService<GotNextDBContext>();



    }

GoogleTokenValidator.cs

public class GoogleTokenValidator : ISecurityTokenValidator
{
    private readonly JwtSecurityTokenHandler _tokenHandler;
    private readonly IEnumerable<string> _clientIds;


    public GoogleTokenValidator()
    {
        _tokenHandler = new JwtSecurityTokenHandler();
    }
    public GoogleTokenValidator(IEnumerable<string> clientIds)
    {
        _tokenHandler = new JwtSecurityTokenHandler();
        _clientIds = clientIds;

    }

    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() { Audience = _clientIds }).Result; // here is where I delegate to Google to validate

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

        try
        {
            var principle = new ClaimsPrincipal();
            principle.AddIdentity(new ClaimsIdentity(claims));
            return principle;
        }
        catch (Exception e)
        {

            Console.WriteLine(e);
            throw;

        }
    }
}

}

I am hitting the endpoint from the belowHttpClient call in Xamarin Forms.

using (var client = new HttpClient() { BaseAddress = new Uri("https://gotnext.azurewebsites.net/api/user/post/") })
            {
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Add(verificationToken.tokenName, verificationToken.token);
                //client.DefaultRequestHeaders.Add("X-ZUMO-AUTH", googleUser.GoogleAuthToken);
                client.DefaultRequestHeaders.Add("Bearer", googleUser.GoogleIdToken);
                var json = JsonConvert.SerializeObject(newUser);
                var content = new StringContent(json, Encoding.UTF8, "application/json");



                response = client.PostAsync(client.BaseAddress, content).Result;

            }
  • Just out of interest why are you using a JWT issued by Google? Why not let Google authenticate your users but then issue your own tokens. It's your API, not Google. Is there an actual need in your app for this or do you only think this is the way it's done based on the SO question you linked to? – Brad May 30 '18 at 04:44
  • @Brad I'm pretty new to authentication via Social accounts. I thought this was the proper way to do it. Is there a better way? – Napoleon Ike Jones May 30 '18 at 10:10
  • Personally, I use [IdentityServer4](http://docs.identityserver.io/en/release/). It's a good solution for all authentication methods and they have examples for social logins. – Brad May 30 '18 at 10:46
  • @Brad Looking at IdentityServer4, it seems like its a much easier and full featured solution. I'm not really able find any documentation on how to add this to an existing solution. Is it just as simple as adding a new IdentityServer project and setting the the port to 5000? – Napoleon Ike Jones May 30 '18 at 15:51
  • The link I provided is the documentation. It contains setup guides and there are full solution examples on Github. – Brad May 30 '18 at 23:33
  • @Brad I started to implement Identity Server and it caused me to notice I was missing `app.UseAuthentivation();` I added that and started receiving 404 errors. The 404s seem to be caused by the antiforgery validation. I turned it off to text and everything is working. – Napoleon Ike Jones May 31 '18 at 00:06
  • @NapoleonIkeJones what was the answer....i too am getting this error – Rohit Kumar Nov 03 '22 at 20:21

2 Answers2

0

The ClaimsIdentity that you add to your ClaimsPrincipal does not have an AuthenticationType set so IsAuthenticated will always be false (source).

return new ClaimsPrincipal(new ClaimsIdentity(claims, "Google"));

You can set the authentication type to any value as long as it's not empty.

Brad
  • 4,493
  • 2
  • 17
  • 24
  • Thanks for the quick response. I made the suggested changes and am still getting the same outcome. When looking at App Insights, I'm seeing this response message, "Authorization failed for user: (null)." – Napoleon Ike Jones May 30 '18 at 00:49
  • What does The `[Authorize]` filter in your controller look like? – Brad May 30 '18 at 00:57
  • just a plain [Authorize] attribute on the class. I just noticed I have it on the Classic and method. Could this be causing an issue? – Napoleon Ike Jones May 30 '18 at 01:10
  • If it's on the class you don't need it on the method but it won't cause an issue. The `validatedToken = null;` line doesn't look right...I'm not sure what impact that may have during authentication. There should be errors in the output console indicating why authentication failed. – Brad May 30 '18 at 01:47
  • Stepping throw the code locally, this line: `return new ClaimsPrincipal(new ClaimsIdentity(claims, "Google"));` Is returning an authenticated identity. However it still is returning a 401. I feel like I'm so close!! – Napoleon Ike Jones May 30 '18 at 03:50
0

Stepping throw the code locally, this line: return new ClaimsPrincipal(new ClaimsIdentity(claims, "Google")); Is returning an authenticated identity. However it still is returning a 401. I feel like I'm so close!!

I am making the authentification with google in my web application too(and i walked from there: enter link description here) and i spended much time to investigate these all. In your solution just add these pieces:

1)

//at "ConfigureServices" in "Startup.cs":
services.AddAuthorization(options =>
        {
            options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
                .RequireAuthenticatedUser()
                .Build();
        });

2) How was said above that it is requied too

return new ClaimsPrincipal(new ClaimsIdentity(claims, "Google"));
  • On 2) I believe Сергей is referring to changing from : principle.AddIdentity(new ClaimsIdentity(claims)); to principle.AddIdentity(new ClaimsIdentity(claims, "Google")); I got that changed on mine and it fixed the 401s. – Deovandski Feb 08 '22 at 14:33