How to update user attribute as an admin via the Cognito SDK - javascript

I am creating an angular app, in which I want to add authentication via AWS Cognito (I am pretty new to AWS). I successfully added functionality for sign-up, sign-in, sign-out, mfa and more. In addition I want to create something like admin panel, where admins can change general users` attributes. But I am not sure how to implement these admin things. How should admins sign-in? How should admins sign-up? Is there a dedicated user pool for them? And then how to manage (change attributes of) the general users as an admin?
I have gone trough the AWS Documentation, but it is not clear enough. I see that there is some kind of actions prefixed with Admin like AdminUpdateUserAttributes but I am not really sure how to use them.
Edit: I have tried something like this:
const AWS = require('aws-sdk');
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});
let params = {
UserAttributes: [{
Name: 'custom:state',
Value: this.newValue
}],
UserPoolId: 'us-east-1_example',
Username: this.username
};
cognitoIdentityServiceProvider.adminUpdateUserAttributes(params, function(err, data) {
// do something with result
err && console.error(err);
data && console.log(data);
});
But I am getting the following error: CredentialsError: Missing credentials in config
How should I set these credentials?

In order to have admin permissions, you need to provide accessKeyId and secretAccessKey or idToken.
One way to do this, is to get these keys from the AWS Management console. They can be extracted from an IAM role, which has the permissions to modify the desired User Pool.
Then you can do:
AWS.config.update({accessKeyId: '...', secretAccessKey: '...'});
What I have personally done in my app is to create another user pool for admins. Then I added this user pool as Identity Provider to an Identity Pool. Then I edited the Authorized IAM role to be able to edit the user pool with the general users.
The following worked for me:
const userPool = new CognitoUserPool({
UserPoolId: this.adminUserPoolId,
ClientId: this.adminClientId
});
const authenticationDetails = new AuthenticationDetails({
Username: username,
Password: password
});
const cognitoUser = new CognitoUser({
Username: username,
Pool: userPool
});
cognitoUser.authenticateUser(authenticationDetails, ....);
const jwt = cognitoUser.getSignInSession().getIdToken().getJwtToken();
const provider = `cognito-idp.${this.region}.amazonaws.com/${this.adminUserPoolId}`;
AWS.config.update({
credentials: new CognitoIdentityCredentials({
IdentityPoolId: this.identityPoolId,
Logins: {
[provider]: jwt // when you provide the jwt, accessKeyId and secretAccessKey are extracted
}
})
});
const identityService = new CognitoIdentityServiceProvider();
identityService.adminUpdateUserAttributes(...);

Related

Singing in with one login to multiple firebase projects respectively cloud firestore

I am using google identity provider to sign into firebase projects. Now I would like to use one identity provider project to sign into multiple cloud firestores. Mainly I want user to be able to sign up for a test environment with the same account as they do on production.
I checked the solution here: Firebase Auth across multiple projects with different Providers but unfortunately it's not working for me. I am getting "This operation is restricted to administrators only."
Currently my code looks as following:
DB / Firebase setup
constructor() {
this.app = firebase.initializeApp(environment.firebase);
this.database = firebase.initializeApp(environment.database, 'secondary');
}
DB Auth
private async initializeDb(firebaseUser) {
const token = await firebaseUser.getIdToken();
const provider = new firebase.auth.OAuthProvider('oidc.prod-login');
const credential = provider.credential({ idToken: token });
await this.firebaseService.database.auth().signInWithCredential(credential);
return firebaseUser;
}
In my test environment I configured the OIDC provider as following:
name: prod-login
client ID: main-firebase-project-id
issuer: https://securetoken.google.com/main-firebase-project-id
Did I miss something?
You can use Firebase admin OR Firebase functions with firebase admin configured to generate Custom tokens with the same User ID token. Then use this token to authenticate with the second project on the client side.
First, initialize the Firebase Admin for the first project Like the one provided here :
const admin = require("firebase-admin");
admin.initializeApp({
credential: admin.credential.cert({
"projectId": "<PROJECT_ID>",
"privateKey": "-----BEGIN PRIVATE KEY-----<KEY>-----END PRIVATE KEY-----\n",
"clientEmail": "foo#<PROJECT_ID>.iam.gserviceaccount.com"
}),
databaseURL: "https://<DATABASE_NAME>.firebaseio.com"
});
Verify the user's ID token from the First Project using Firebase Admin and Use the verified user's ID token to create a custom token for the second project then Pass the custom token to the client SDK:
const idToken = "userId-token-from-FirstProject";
admin.auth().verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// Create a custom token for the second project.
return admin.auth().createCustomToken(uid, { projectId: "<PROJECT_ID_2>" });
})
.then((customToken) => {
// Pass the custom token to the client SDK
res.send({ token: customToken });
})
.catch((error) => {
console.error("Error creating custom token:", error);
res.status(500).send({ error: error });
});
Use the custom token to authenticate with the second project on the client-side of second project:
firebase.auth().signInWithCustomToken(customToken)
.then((result) => {
// The user is authenticated with the second project.
console.log("User signed in with custom token:", result.user.toJSON());
})
.catch((error) => {
console.error("Error signing in with custom token:", error);
});
Although this will work, I think there should be a delay given for the Client side to authenticate with the seconds project as we are re-authenticating with a custom token.
For different OIDC providers I think this answer makes more sense
For info go through these links which also covers this topic:
Google groups discussion
Firebase Docs

How to authenticate API calls with Cognito using Facebook?

I want to authenticate users using Cognito, with option to use Facebook. User can sign_in/sign_up using either of those options.
I have created Cognito User Pool and Cognito Federated Identity, and also I have created Facebook App for authentication. Both User Pool and Facebook app are connected to Federated identity.
When I sign_up and later authenticate Cognito User via Cognito User Pool, then Cognito returns accessToken, which I store in localStorage on front and use whenever needed for athentication.
I have /authenticate endpoint (express), that takes in username & password, and returns accessToken if all went well. Whenever I make API call that requires auth, I send accessToken that I have in local storage. It goes, more or less as this:
// POST user/authenticate
const authenticationData = {
Username: username,
Password: password
}
authenticationDetails = new AuthenticationDetails(authenticationData)
const userData = {
Username: username,
Pool: userPool()
}
cognitoUser = new CognitoUser(userData)
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (res) => resolve(res), // here I get accessToken
onFailure: (err) => {
console.log('[authenticateUser error]', err)
reject(err)
},
//...
However
When I use Facebook, I do not get accessToken I could use in same fashion. I get accessToken from Facebook via FB.login, I pass it to Cognito to authenticate, and then I don't know what to do, because I cannot get any token that could be used to authenticate API calls, that require Cognito Authentication.
Here's what I do:
await window.FB.login((response) => {
props.userFacebookSignIn(response)
})
// ...
call(foo, 'users/facebook_sign_in', { accessToken: payload.facebookAccessToken })
// ...
// users/facebook_sign_in
AWS.config.region = config.AWSRegion
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'foo',
Logins: {
'graph.facebook.com': facebookAccessToken
}
})
AWS.config.credentials.get((err) => {
// Here I get no errors, I presume that I have logged Facebook user in
const accessKeyId = AWS.config.credentials.accessKeyId
const secretAccessKey = AWS.config.credentials.secretAccessKey
const sessionToken = AWS.config.credentials.sessionToken
// here I can do stuff probably,
// but I would like to receive token that would allow me to do stuff,
// rather than context I can do stuff in
})
While I am doing all of this, I have this feeling, that devs at AWS implemented Cognito as frontend solution, rather than something to be used in backend. Correct me if I am wrong.
Nevertheless, I would like to be able authenticate api calls using Cognito and Facebook interchangeably in express middleware.
Is that possible? Thanks.
I have used federated identity for salesforce single sign on but i imagine the steps will the same. After authenticating with facebook you will recieve and id_token from them in response. You have to pass this as a parameter in the getId method:
var params = {
IdentityPoolId: 'STRING_VALUE', /* required */
AccountId: 'STRING_VALUE',
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* 'graph.facebook.com': ... */
}
};
cognitoidentity.getId(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
In the result you will get an identity id which you can save somewhere so that you don't have to make this call everytime while authenticating. Now take this identity id and make the getCredentialsForIdentity call:
response = client.get_credentials_for_identity(
IdentityId='string',
Logins={
'string': 'string'
},
CustomRoleArn='string'
)
This will finally give you the temporary access key, secret key and session key you need.
I decided to use oAuth.
Here's quick & dirty look on how it's done
In AWS Cognito
1) Set up Cognito User Pool. Add App Client save App client id & App client secret as COGNITO_CLIENT_ID and COGNITO_CLIENT_SECRET
2) Go to Federation > Identity providers and add your Facebook app ID and App secret (both you will find in Facebook app panel)
3) Go to App integration > App client settings click "Select all", set up your Callback URL, mine is localhost:5000/facebook also select Authorization code grant and Allowed OAuth Scopes (save scopes to say: COGNITO_SCOPES)
4) Now go to App integration > Domain name and enter your custom domain; let's say example-app-debug so it's: https://example-app-debug.auth.us-east-1.amazoncognito.com
That's all there is to Cognito
no the Facebook part
5) Settings > Basic add example-app-debug.auth.us-east-1.amazoncognito.com to your App domains - Save Changes
6) In Facebook Login > Settings in Valid OAuth Redirect URIs add this URL: https://example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/idpresponse and Save Changes
and the code
In browser, redirect user to this url when Login w. Facebook button is clicked:
window.location.href =
`https://example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/authorize` +
`?identity_provider=Facebook` +
`&redirect_uri=http://localhost:5000/facebook` +
`&response_type=code` +
`&client_id=${COGNITO_CLIENT_ID}` +
`&scope=${COGNITO_SCOPES}`
this call should come back to you with a code, like this: http://localhost:5000/facebook?code=foo-bar-code Send this code to your backend.
In backend, do this:
const axios = require('axios')
const url = `` +
`https://${COGNITO_CLIENT_ID}:${COGNITO_CLIENT_SECRET}` +
`#example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/token` +
`?grant_type=authorization_code` +
`&code=foo-bar-code` + // <- code that came from Facebook
`&redirect_uri=http://localhost:5000/facebook` +
`&client_id=${COGNITO_CLIENT_ID}`
const response = await axios.post(url)
// response should have access_token, refresh_token and id_token in data
You send access_token, refresh_token and id_token back to frontend and save them in local storage and use them to authenticate and Done.

Firebase, Auth0, React. The custom token format is incorrect. Please check the documentation

I’m trying to use Auth0 JWT Tokens with Firebase, with no much luck.
When using the token with Firebase:
const token = localStorage.getItem('id_token'); //from auth0
firebase.auth().signInWithCustomToken(token).catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
console.log(token);
});
All I get is:
“The custom token format is incorrect. Please check the documentation.”
As far as I saw in Firebase’s documentation Auth0 and Firebase tokens are different:
https://firebase.google.com/docs/auth/admin/create-custom-tokens
Apparently, Firebase expects an uid which is not present in the one generated by Auth0 which uid equivalent is in sub.
I tried to create a rule to modify the Auth0’s token to include a copy of sub named uid to see if this could be a solution, but it’s not working, nothing is added to the body of the token.
function (user, context, callback) {
context.idToken.uid = user.user_id;
callback(null, user, context);
}
Any idea / suggestion?
PS:
1.I checked the token in jwt.io and its valid.
2.I tried reducing the expiring time to less than 5min, as I saw some people considering this a possible solution, but its not.
You can't use an Auth0 token directly with Firebase. You need to create a server-side API that uses the firebase-admin SDK to create a Firebase Custom Token using the Auth0 data.
There's a full tutorial over on the OAuth site. Check out the API Routes section on how to use firebaseAdmin.auth().createCustomToken given the OAuth token:
// Auth0 athentication middleware
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: config.AUTH0_API_AUDIENCE,
issuer: `https://${config.AUTH0_DOMAIN}/`,
algorithm: 'RS256'
});
// Initialize Firebase Admin with service account
const serviceAccount = require(config.FIREBASE_KEY);
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: config.FIREBASE_DB
});
// GET object containing Firebase custom token
app.get('/auth/firebase', jwtCheck, (req, res) => {
// Create UID from authenticated Auth0 user
const uid = req.user.sub;
// Mint token using Firebase Admin SDK
firebaseAdmin.auth().createCustomToken(uid)
.then(customToken =>
// Response must be an object or Firebase errors
res.json({firebaseToken: customToken})
)
.catch(err =>
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
})
);
});

AWS Cognito Sync with Authorised Users

I have implemented AWS Cognito successfully as below for Unauthorised users. With Unauthorised users, once the IdentityId is successfully returned I can then use Cognito Sync to create and manipulate Datasets successfully. I have been unable to use Cognito Sync with an Authorised user. Please can someone tell me how I obtain the IdentityId for a Authorised user, which then allows me to listRecords (which requires the IdentityId). IdentityId is always being returned as undefined.
AWS.config.credentials = new CognitoIdentityCredentials({
IdentityPoolId: REACT_APP_AWS_CONFIG_IDENTITY_POOL_ID,
});
const userPool = new CognitoUserPool({
UserPoolId: REACT_APP_AWS_CONFIG_USER_POOL_ID,
ClientId: REACT_APP_AWS_CONFIG_CLIENT_ID,
});
let cognitosync = new AWS.CognitoSync();
AWS.config.credentials.get(()=>{
IdentityId = AWS.config.credentials.identityId;
listRecords();
});
function listRecords(){
cognitosync.listRecords({
DatasetName : DATA_SET_NAME,
IdentityId : IdentityId,
IdentityPoolId : REACT_APP_AWS_CONFIG_IDENTITY_POOL_ID
}, function(error, data) {
//...
});
}
thanks in advance
I suspect you just need to add a Logins map to your AWS.config.credentials object for authenticated users.
What you put in the Logins map depends on how the user authenticated, but essentially you need a single entry, with the identity provider (IDP) as the key, and the id token you received from that IDP as the value.
For example, if your user authenticated with Google, you might have:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
Logins: {
'accounts.google.com': googleUser.tokenId
}
});
where googleUser is the object returned by Google.
If your user authenticated with a Cognito User Pool, you might have:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
Logins: {
'cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx':
session.getIdToken().getJwtToken()
}
});
Where session is passed to a callback you provide to CognitoUser.getSession(). Additional code context for this use case in this Gist.
I've discovered what my problem was. To get the JWT for the Login key/value I was using :
result.getAccessToken().getJwtToken()
when it should have been:
result.getIdToken().getJwtToken()
all is working now

How to delete amazon cognito user?

I want to delete cognito user using my nodejs application.
Example
cognitoUser.deleteUser (err, result) ->
if err
reject err
resolve result
when i try to delete cognito user error throws as follows
Error: User is not authenticated
cognitoUser.deleteUser is used by an authenticated user to delete himself but i want to delete all users using super user
Please give me some idea to solve this problem.
You can use the main aws javascript SDK and call the adminDeleteUser operation. It is an authenticated operation and it will require developer credentials for you to call it.
https://github.com/aws/aws-sdk-js/blob/master/apis/cognito-idp-2016-04-18.normal.json#L100
var aws = require('aws-sdk');
var CognitoIdentityServiceProvider = aws.CognitoIdentityServiceProvider;
var client = new CognitoIdentityServiceProvider({ apiVersion: '2016-04-19', region: 'us-east-1' });
//now you can call adminDeleteUser on the client object
You can do from AWS-CLI
aws cognito-idp admin-delete-user --user-pool-id ${user pool id} --username ${username usually email here}

Categories