dimeac
dimeac

Reputation: 51

How to validate Cognito JWT with empty audience correctly and authenticate after?

I use aps.net core with JWT authentication and I found that aws cognito returns wrong token. Instead aud it returns client_id in access token. I use Nuget libraries

It was the same result. For examaple: Access token is:

{
  "sub": "9ed87b45-da04-4fda-bc74-XXXXXXXXXXXX",
  "event_id": "469880d0-8b17-417a-88d7-XXXXXXXXXXXX",
  "token_use": "access",
  "scope": "aws.cognito.signin.user.admin",
  "auth_time": 1583252488,
  "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_XXXXXXXX",
  "exp": 1583256088,
  "iat": 1583252488,
  "jti": "c1ca9561-51ce-4b57-9f51-3355363fb4f6",
  "client_id": "AppClientIDXXXXXXXXXXXXX",
  "username": "testname"
}

After all I found that id token returns with 'aud'

{
  "sub": "9ed87b45-da04-4fda-bc74-XXXXXXXXXXXX",
  "aud": "AppClientIDXXXXXXXXXXXXX",
  "email_verified": true,
  "event_id": "469880d0-8b17-417a-88d7-XXXXXXXXXXXX",
  "token_use": "id",
  "auth_time": 1583252488,
  "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_XXXXXXXX",
  "cognito:username": "testname",
  "exp": 1583256088,
  "iat": 1583252488,
  "email": "[email protected]"
}

I used two way of adding jwt authentication. It didn't work for me. Example 1:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
     options.TokenValidationParameters = new TokenValidationParameters
     {
         ValidateIssuerSigningKey = true,
         IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
         {
             var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
             return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
         },
         ValidateIssuer = true,
         ValidIssuer = $"https://cognito-idp.{region}.amazonaws.com/{poolId}",
         ValidateAudience = true,
         ValidAudience = appClientId,
     };
});

Example 2:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
     options.Audience = appClientId;
     options.Authority = $"https://cognito-idp.{region}.amazonaws.com/{poolId}";
});

My authentication code

using AWSSDK.CognitoIdentityProvider

var initiateAuthRequest = new InitiateAuthRequest()
    {
        ClientId = myClientId,
        AuthFlow = AuthFlowType.USER_PASSWORD_AUTH,
    };
initiateAuthRequest.AuthParameters.Add("USERNAME", user.Username);
initiateAuthRequest.AuthParameters.Add("PASSWORD", user.Password);
var authResponse = await _cognitoIdentityProvider.InitiateAuthAsync(initiateAuthRequest);

using Amazon.Extensions.CognitoAuthentication

var provider = new AmazonCognitoIdentityProviderClient(new EnvironmentVariablesAWSCredentials(), myRegion);
var userPool = new CognitoUserPool(myPool, myClient, provider);
var usr = new CognitoUser(user.Username, myClient, userPool, provider);
AuthFlowResponse authResponse = await usr.StartWithSrpAuthAsync(
    new InitiateSrpAuthRequest(){Password = user.Password}).ConfigureAwait(false);

How to get valid JWT token? How to validate token correctly?

Updated: In Example 1 if I set ValidateAudience to false and removed ValidAudience I get 401 error

ValidateAudience = false,
//ValidAudience = appClientId

My Startup.cs is

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public static IConfiguration Configuration { get; private set; }

    public void ConfigureServices(IServiceCollection services)
    {
        var region = Configuration[Resources.AWSRegion];
        var poolId = Configuration[Resources.AWSPoolId];
        var appClientId = Configuration[Resources.AWSClientId];

        services.AddSingleton<IAmazonCognitoIdentityProvider>(provider => new AmazonCognitoIdentityProviderClient(RegionEndpoint.USEast2)));

        services.AddControllers();
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
             options.TokenValidationParameters = new TokenValidationParameters
             {
                 ValidateIssuerSigningKey = true,
                 IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
                 {
                     var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
                     return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
                 },
                 ValidateIssuer = true,
                 ValidIssuer = $"https://cognito-idp.{region}.amazonaws.com/{poolId}",
                 ValidateAudience = true,
                 ValidAudience = appClientId
             };
        });
        services.AddAuthorization();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
        app.UseAuthentication();
    }
}

Upvotes: 5

Views: 3088

Answers (4)

Javier Pazos
Javier Pazos

Reputation: 169

I read several answers and the majority recomended setting

ValidateAudience = false

Since I didn't want to do that, because it will avoid validating the correct audience, I ended up defining AudienceValidator, to do it you have to define a delegate in the options when configuring your application. So in Startup you have to include this:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Audience = configuration["AWS:Cognito:ClientId"];
                    options.Authority = configuration["AWS:Cognito:Authority"];
                    options.TokenValidationParameters.AudienceValidator = (audiences, securityToken, validationParameters) =>
                    {
                        //This is necessary because Cognito tokens doesn't have "aud" claim. Instead the audience is set in "client_id"
                        return validationParameters.ValidAudience.Contains(((JwtSecurityToken)securityToken).Payload["client_id"].ToString());
                    };
                });

This will check that the claim "client_id" corresponds with the audience you're expecting

Upvotes: 3

Nikita Komarov
Nikita Komarov

Reputation: 11

I think it is because you use the access jwt token returned from Cognito during sign-in. Use id token instead.

return new Promise((resolve, reject) => {
            return user.authenticateUser(authenticationDetails, {
                onFailure: (err) => {
                    reject(err);
                },
                onSuccess: (result) => {
                    resolve(new SignInResponse(result.getIdToken().getJwtToken()));
                },
            });
        });

Upvotes: 1

Vlad Pecherytsia
Vlad Pecherytsia

Reputation: 1

I had the same issue and I have found only one solution.

If you are going to use an access token to authenticate user, set

ValidateAudience = false,
//ValidAudience = appClientId

and add AuthenticationSchemes to Authorize filter like that:

[HttpGet]
[Authorize(AuthenticationSchemes = "Bearer")]
public async Task<string> Test()
{
    return await Task.Run(() => "hello world");
}

I have very similar code and it worked for me. Hope it helps for you as well.

Also you can replace this code:

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

To that (just to simplify):

services.AddAuthentication("Bearer")

Upvotes: 0

Sviatoslav Mykhailiv
Sviatoslav Mykhailiv

Reputation: 13

This worked out for me, I used id token instead of access token

public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options =>
        {
            options.TokenValidationParameters = GetCognitoTokenValidationParams();
        });
    }

    private TokenValidationParameters GetCognitoTokenValidationParams()
    {
        var cognitoIssuer = $"https://cognito-idp.{Configuration["AWS:Region"]}.amazonaws.com/{Configuration["AWS:UserPoolId"]}";
        var jwtKeySetUrl = $"{cognitoIssuer}/.well-known/jwks.json";
        var cognitoAudience = Configuration["AWS:UserPoolClientId"];

        return new TokenValidationParameters
        {
            IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
            {
                // get JsonWebKeySet from AWS
                var json = new WebClient().DownloadString(jwtKeySetUrl);
                return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
            },
            ValidIssuer = cognitoIssuer,
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateLifetime = true,
            ValidAudience = cognitoAudience
        };
    }

Upvotes: 1

Related Questions