I've created a lambda that retrieves user attributes as (username, email, name...etc) however, I wonder how it's possible to get user attributes without explicitly hardcoding sub value to get all other related attributes? do I need to decode JWT Cognito token in frontend and use it in the lambda to determine the correct user and retrieve the related attributes?
here is my lambda in Node.JS:
const AWS = require('aws-sdk');
exports.handler = function(event, context) {
var cog = new AWS.CognitoIdentityServiceProvider();
var filter = "sub = \"" + "UserSUB" + "\"";
var req = {
"Filter": filter,
"UserPoolId": 'POOL here',
};
cog.listUsers(req, function(err, data) {
if (err) {
console.log(err);
}
else {
if (data.Users.length === 1){
var user = data.Users[0];
var attributes = data.Users[0].Attributes;
console.log(JSON.stringify(attributes));
} else {
console.log("error.");
}
}
});
}
I think the proper way to do this depends on whether you want to use API Gateway or not (It will make things simpler IMHO).
If you don't want to use APIG, and you are calling the lambda directly using temporary credentials, then you should pass the entire ID token and have the lambda do all of the validation and decoding (probably using a third party library for JWTs). It's not safe to do it in the frontend as that would mean you have a lambda that blindly accepts the attributes as facts from the frontend, and a malicious user could change them if they wanted.
If you are using API Gateway to put lambdas behind an API then I would create a cognito authorizer based on the User Pool, create a resource/method and configure it to use the authorizer, and enable Use Lambda Proxy Integration for the Integration Request. All the token's claims enabled for the client will be passed through on event.requestContext.authorizer.claims so long as it's valid.
There are some AWS docs here, although this does not use proxy integration. If you use proxy integration then you can skip 6b as the APIG will set the values for you. This is described in an answer here.
Related
I'd like to attach an IoT policy to the Cognito identities given to the federated users of my app. I'm tryng to do this with a Lambda function in the Post confirmation trigger of my user pool. Here's my function so far:
const AWS = require('aws-sdk');
const iot = new AWS.Iot();
exports.handler = async function(event, context) {
const policyName = 'arn:aws:iam::XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const target = context.identity.cognitoIdentityId;
await iot.attachPolicy({ target, policyName }).promise();
const response = {
statusCode: 200,
body: JSON.stringify('Policy attached.'),
};
return response;
};
When this function runs I get an error:
"cannot read property 'cognitoidentityid' of undefined"
Similar error if I define principal as
const principal = context.cognito_identity_id; //error: "Missing required key 'target' in params"
According to the Iot docs, "The context object in your Lambda function contains a value for context.cognito_identity_id when you call the function with AWS credentials that you obtain through Amazon Cognito Identity pools." Can anyone tell me how to do that?
I should add that I would like to attach this policy for both desktop and mobile users.The Lambda docs imply that the identity property of the context object is provided for mobile apps only. If that is true then is there a different way to attach the IoT policy to all Cognito identites, mobile and desktop alike?
Thanks
To sum up, You will not be able to get identity id in Cognito's post confirmation trigger.
To overcome it, Client can invoke separate Lambda function (once user is confirmed) and in that Lambda you can attach policy, because here you will get identity id.
Other alternative which I don't prefer is client themselves attach a policy after user confirmation.
Attach Policy API - https://docs.aws.amazon.com/iot/latest/apireference/API_AttachPolicy.html
I have a Asp.Net Web API 2 using Token based authentication (OAuth2).
I have implemented Web API versioning using aspnet-api-versioning.
So now I have three different versions of my API. It's really great, I can now change V3 without affecting the current API.
But the /token endpoint is not versioned because it is not in my controller. It's in the Providers.
I searched but couldn't find anything helpful.
We can register more than one token endpoint in the Startup.Auth.cs
So here's what I did:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(TokenExpirationInDays),
AllowInsecureHttp = true, //Allow HTTP to send username password.
};
app.UseOAuthBearerTokens(OAuthOptions);
OAuthOptionsV3 = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/V3/Accounts/Token"),
Provider = new ApplicationOAuthProvider2(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(TokenExpirationInDays),
AllowInsecureHttp = true, //Allow HTTP to send username password.
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptionsV3);
So now I have different token endpoint for each version.
I'm not familiar with this particular setup, but this looks like middleware. There isn't quite enough information here to provide you with a specific answer, but your goals should be achievable in one of a few ways:
Option 1 - Use the Conventions API
If you authorization endpoint is actually a controller (though I think it is not), you can use the Conventions API like so:
services.AddApiVersioning(options =>
{
options.Conventions.Controller<OAuthController>().IsApiVersionNeutral();
}
Conventions was specifically meant to deal with a scenario where a controller might be externally defined and you don't have any control over the source code.
Option 2 - Use a Custom Convention
Middleware could create actions dynamically. As long as actions are actually produced, then you can use a custom IControllerConvention. You would be passed the ControllerModel which contains the actions you need to version. Assuming this is the correct behavior, you'd be looking for matching actions in the source model and then you can apply it to the controller conventions with something like:
public class MyConventions : IControllerConvention
{
public bool Apply(IControllerConventionBuilder controller, ControllerModel controllerModel)
{
var method = // TODO: resolve the target method from controllerModel
if (method == null)
{
return false;
}
controller.Action(method).IsApiVersionNeutral();
return false;
}
}
Option 3 - In Middleware
If this is pure middleware, API versioning isn't directly supported there. You can, however, support versioning on your own if the pipeline is composed properly. Specifically, API Versioning must come before other parts of middleware that need it. This usually happens automatically, but if you need to control registration, you need to change your setup to handle it manually like this:
services.AddApiVersioning(options => options.RegisterMiddleware = false);
// ... inside application setup
services.UseApiVersioning();
The API Versioning middleware doesn't really do much of anything special. It merely adds a pipeline feature. As long as that's before your other middleware, it will be available downstream like this:
var feature = context.Features.Get<IApiVersioningFeature>();
// the raw, unparsed API version, if any
var rawApiVersion = feature.RawApiVersion;
// the parse API version; will be null if no version is specified
// or the value cannot be parsed
var apiVersion = feature.ApiVersion;
// TODO: enforce versioning policies within the middleware
Option 4 - Use the API Explorer
If none of the previous approaches will work for you, you can leverage the API Explorer extensions for API Versioning to build your configuration (as above) from discovered APIs. This would have the advantage of not being hardcoded or require changes every time you release a new version.
Your application startup configuration would change to something like this:
public void Configure(IApplicationBuilder app, IApiVersionDescriptionProvider provider)
{
foreach (var description in provider.ApiVersionDescriptions)
{
var options = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString($"/api/{description.GroupName}/Accounts/Token"),
Provider = new ApplicationOAuthProvider2(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(TokenExpirationInDays),
AllowInsecureHttp = true,
};
app.UseOAuthBearerTokens(options);
}
}
In AWS API Gateway I am developing lambda function for custom authorizer using .NET Core. The API will receive api-key in query string and my custom authroizer will validate the key. Based on my understanding, after validation is done the lambda function needs to return IAM policy. The awslab blurprint does not have any example for .NET core. The only example i found so far is GrandmasRecipes that is using JWT Token.
I would like to know what IAM policy the lambda function needs to return and are there any corresponding .NET Core classes for request and response?
Update 1
So below is my code for custom lambda authorizer. However i would like know:
1> What should be PrincipalID. Currently i am just setting it to User
2>CheckAuthorization method gets all the keys from aws and only check the existence by comparing the key from the request. It should also check the Usage Plans and make sure the key from the request is configured in Usage Plans
3>The role that this Authorizer is executing under is attached to AmazonAPIGatewayAdministrator policy so that it can get API Keys, whats the minimum policy do i need for this role to validate api-key?
4>Is there any in-built method in AWSSDK to do validate api-key correctly?
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
namespace ApikeyAuthorizer
{
public class Function
{
public async Task<APIGatewayCustomAuthorizerResponse> FunctionHandler(APIGatewayCustomAuthorizerRequest authEvent, ILambdaContext context)
{
var key = authEvent.QueryStringParameters["key"];
bool authorized = await CheckAuthorization(key);
var authPolicy = new APIGatewayCustomAuthorizerResponse();
authPolicy.PrincipalID = "user";
authPolicy.PolicyDocument = new APIGatewayCustomAuthorizerPolicy();
authPolicy.PolicyDocument.Version = "2012-10-17";
authPolicy.PolicyDocument.Statement = new List<APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>();
if (authorized)
{
var statement = new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement();
statement.Action = new HashSet<string>(new string[] { "execute-api:Invoke" });
statement.Effect = "Allow";
statement.Resource = new HashSet<string>(new string[] { "arn:aws:execute-api:us-east-1:xxxxx:*/*/GET/*" });
authPolicy.UsageIdentifierKey = key;
authPolicy.PolicyDocument.Statement.Add(statement);
}
else
{
var statement = new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement();
statement.Action = new HashSet<string>(new string[] { "execute-api:Invoke" });
statement.Effect = "Deny";
statement.Resource = new HashSet<string>(new string[] { "arn:aws:execute-api:us-east-1:xxxxx:*/*/GET/*" });
authPolicy.PolicyDocument.Statement.Add(statement);
}
return authPolicy;
}
public async Task<bool> CheckAuthorization(string key)
{
Amazon.APIGateway.AmazonAPIGatewayClient client = new Amazon.APIGateway.AmazonAPIGatewayClient();
var response = await client.GetApiKeysAsync(new Amazon.APIGateway.Model.GetApiKeysRequest()
{
IncludeValues = true
});
foreach (var apiKey in response.Items)
{
if (apiKey.Value == key)
{
return true;
}
}
return false;
}
}
}
You don't need to use a Lambda Authorizer to validate API Key neither it should be used for authorisation. You can do following to configure API key validation in API Gateway.
In you API Resources section, set API Key Required true for the methods where you want to enable it
Go to API Keys section, select Create API key from Actions dropdown and create a key
Go to Usage Plans section and create a new usage plan.
After you've created a usage plan click on it and then click API Keys tab. Here click Add API Key to Usage Plan and add the key you created in step # 2
Now click on Details tab then click Add API Stage. Select your API and stage that you want to restrict with API Key.
Your API methods are now required an x-api-key HTTP header where you've enabled it. When you request API endpoint make sure you add x-api-key header with same value you have created in step 2 above. If you don't add this header or put a wrong value you will get 403 Forbidden error.
curl -X PUT \
https://XXXX.XXXXX-api.ca-central-1.amazonaws.com/PROD/XXX-microservice \
-H 'Content-Type: application/json' \
-H 'x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-d '{
"initData": "HI",
"name": "vaquar khan",
"likes": "Java"
}'
Security key validation taken care by API getaway so no lambda
authorizer required
Please dont create duplicate question just update old queston ,I have answred in your question here :
- How to validate API Key in AWS Lambda function
I have a mobile app that talks to a backend web API (core 2.0). Presently I have the API configured to use Opendidict with Facebook integration based on the configuration listed below.
public static IServiceCollection AddAuthentication(this IServiceCollection services, AppSettings settings)
{
services.AddOpenIddict<int>(options =>
{
options.AddEntityFrameworkCoreStores<RouteManagerContext>();
options.AddMvcBinders();
options.EnableAuthorizationEndpoint("/auth/authorize");
options.EnableTokenEndpoint("/auth/token");
options.AllowAuthorizationCodeFlow();
options.AllowImplicitFlow();
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(1));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(20160));
options.DisableHttpsRequirement();
options.AddEphemeralSigningKey();
});
services.AddAuthentication()
.AddFacebook(o => { o.ClientId = settings.FacebookAppID; o.ClientSecret = settings.FacebookAppSecret; })
.AddOAuthValidation();
return services;
}
The password flow works perfectly when they want to use local account. What I'm struggling with is how to return the access/refresh token after successfully authenticating with Facebook. I have the standard account controller with ExternalLogin and ExternalLoginCallback which also works perfectly as I'm able to successfully login and get the local user account it's tied to and signed in.
In my mind, the user clicks facebook login, which calls ExternalLogincallBack, which logs in the user. After that all I want to do is return the access/refresh token just like the password flow.
When I try to use the ImplicitFlow by providing the implicit flow arguments in the redirect (/auth/authorize?...) from ExternalLoginCallback, I can get the access token, but no refresh token even if I specify the offline_scope. From what I read, it seems the implicit flow doesn't support refresh so I tried code flow.
When using the CodeFlow, I can get the code token from the redirect to "/auth/authorize" but can't figure out how to call into the token endpoint from the authorize endpoint to return the access/refresh token directly to the client app. Do I just need to return the code to the client and have them make another call to post to the token endpoint to get access/refresh tokens?
This doesn't feel correct and I'm stumped. Seems like I should be able to just return the access/refresh token after I've signed in externally just like what happens with password flow. Any help would be greatly appreciated as I've been struggling with this for several days.
[HttpGet("~/auth/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
if (!User.Identity.IsAuthenticated)
{
// If the client application request promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPrompt(OpenIdConnectConstants.Prompts.None))
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = OpenIdConnectConstants.Errors.LoginRequired,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The user is not logged in."
});
// Ask OpenIddict to return a login_required error to the client application.
return Forbid(properties, OpenIdConnectServerDefaults.AuthenticationScheme);
}
return Challenge();
}
// Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return BadRequest(new
{
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = "An internal error has occurred"
});
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null)
{
// Create a new ClaimsPrincipal containing the claims that will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme);
if (!request.IsRefreshTokenGrantType())
{
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted to allow OpenIddict to return a refresh token.
ticket.SetScopes(new[]
{
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
ticket.SetResources("RouteManagerAPI");
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them to a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in ticket.Principal.Claims)
{
// Never include the security stamp in the access and identity tokens, as it's a secret value.
if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType)
{
continue;
}
var destinations = new List<string>
{
OpenIdConnectConstants.Destinations.AccessToken
};
// Only add the iterated claim to the id_token if the corresponding scope was granted to the client application.
// The other claims will only be added to the access_token, which is encrypted when using the default format.
if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) ||
(claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) ||
(claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles)))
{
destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken);
}
claim.SetDestinations(destinations);
}
return ticket;
}
When I try to use the CodeFlow, I can get the code token but can't figure out how to call into the token endpoint from the authorize endpoint to return the access/refresh token directly to the client app. Do I just need to return the code to the client and have them make another call to post to the token endpoint to get access/refresh tokens?
That's exactly what you're supposed to do as the code flow is a 2-part process: once your mobile apps has an authorization code, it must redeem it using a simple HTTP call to the token endpoint to get an access token and a refresh token.
OK I am now days into this and have made significant progress but am still completely stumped about the fundamentals.
My application uses Cognito User Pools for creating and managing users - these are identified on S3 it seems by their IdentityId. Each of my users has their own S3 folder, and AWS automatically gives them a folder name that is equal to the user's IdentityId.
I need to relate the IdentityId to the other Cognito user information but cannot work out how.
The key thing I need is to be able to identify the username plus other cognito user attributes for a given IdentityId - and it's insanely hard.
So the first battle was to work out how to get the IdentityId when a Cognito user does a request via the AWS API Gateway. Finally I got that worked out, and now I have a Cognito user, who does a request to the API Gateway, and my Lambda function behind that now has the IdentityId. That bit works.
But I am completely stumped as to how to now access the Cognito user's information that is stored in the user pool. I can't find any clear information, and certainly no code, that shows how to use the IdentityId to get the Cognito user's attributes, username etc.
It appears that if I use a "Cognito user pool" to authorize my method in API Gateway, then the body mapping template can be used to put Cognito User information such as the sub and the username and email address into the context, BUT I do NOT get the IdentityId.
BUT if I use the AWS_IAM to authorize my method in the API gateway then the body mapping template does the inverse - it gives me the IdentityId but not the Cognito user fields such as sub and username and email.
It's driving me crazy - how can I get the IdentityId and all the Cognito users fields and attributes together into one data structure? The fact that I seem to be only able to get one or the other just makes no sense.
It turns out that to get the IdentityId AND user details at the same time using AWS Lambda/Cognito/API Gateway, you need to have a Lambda function that is authenticated using AWS_IAM (NOT COGNITO_USER_POOLS), you must send your request the AWS API Gateway, BUT it MUST be a signed request, you must then modify the integration request body mapping templates so that you are given the IdentityId in the event (maybe the context? can't remember). Now you have the IdentityId. Phew. Now you must submit the client's Cognito ID token from the front end to the back end. It is important to validate the token - you cannot trust that it has not been tampered with if you do not validate it. To decode and validate the token you must get the keys from your userpool, put them into your script, ensure that you have jwt decoding libraries plus signature validation libraries included in your AWS lambda zipfile. Now your script must validate the token submitted from the front end and then you can get the user details out of the token. Voila! Now you have both IdentityId plus user details such as their sub, username and email address. So easy.
The above is what is takes to get the username associated with an IdentityId using AWS Cognito/Lambda/API Gateway. This took me days to get working.
Can I please say to any Amazon employees who wander across this ........ well it's WAY too hard to get the user details associated with an IdentityId. You need to fix this. It made me angry that this was so hard and burned so much of my time.
The solution:
I did this by modifying an Amazon employees custom authorizer here:
https://s3.amazonaws.com/cup-resources/cup_custom_authorizer_lambda_function_blueprint.zip
as found and described here:
https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/
use strict';
let util = require('util');
var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var userPoolId = 'YOUR USERPOOL ID';
var region = 'YOUR REGION'; //e.g. us-east-1
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
//https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
// DOWNLOAD FROM https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
let userPoolKeys = {PUT YOUR DOWNLOADED USER POOL KEYS JSON HERE};
var pems = {};
let convertKeysToPems = () => {
var keys = userPoolKeys['keys'];
for(var i = 0; i < keys.length; i++) {
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
}
exports.handler = function(event, context) {
convertKeysToPems()
console.log(event);
let token = event['body-json'].cognitoUserToken;
console.log(event['body-json'].cognitoUserToken);
ValidateToken(pems, event, context, token);
};
let ValidateToken = (pems, event, context, token) => {
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
console.log(decodedJwt)
if (!decodedJwt) {
console.log("Not a valid JWT token");
context.fail("Unauthorized");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'id') {
console.log("Not an id token");
context.fail("Unauthorized");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log(pems, 'pems');
console.log(kid, 'kid');
console.log('Invalid token');
context.fail("Unauthorized");
return;
}
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
if(err) {
context.fail("Unauthorized");
} else {
let x = decodedJwt.payload
x.identityId = context.identity.cognitoIdentityId
//let x = {'identityId': context['cognito-identity-id'], 'decodedJwt': decodedJwt}
console.log(x);
context.succeed(x);
}
});
}
This problem -- the problem of using the user's sub instead of their identityId in S3 paths and how to set that up -- is 100% solved for me thanks to the help of #JesseDavda's solution to this problem in this issue: https://github.com/aws-amplify/amplify-js/issues/54
For all of you developers who have been trying to get the identityId in lambdas so that your Amplify default paths in S3 work - this solution simply ends up ignoring identityId altogether - it is a solution that sets up the paths in S3 based on sub instead of the identityId. At the end of this solution, you will never have to deal with more than one id for your users, you will never have to deal with identityId (hopefully) ever again.
If I'm understanding this correctly you want the CognitoIdentityId and the User attributes in the same place. How we do it is the following way:
From the event request context we get the IdentityId:
event.requestContext.identity.cognitoIdentityId
Also from the request context we get the user's sub:
event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1]
Then with the sub you can request the rest of the attributes the following way:
const AWS = require('aws-sdk');
let cognito = new AWS.CognitoIdentityServiceProvider();
let request = {
Username: userSub,
UserPoolId: process.env.userPoolId,
};
let result = await cognito.adminGetUser(request).promise();
const userAttributes = result.UserAttributes.reduce((acc, attribute) => {
const { Name, Value } = attribute;
acc[Name] = Value;
return acc;
}, {});
return userAttributes;