I'm using a Cognito userpool and Cognito federated identities to pass an IAM role to authenticated and unauthenticated users. When I try to switch a user from unauthenticated to authenticated, the developer console doesn't register that the change has happened; it is showing that I have 100% unauthenticated users.
Right now I instantiate my AWS client as an unauthenticated user and then call a function to update the credentials when they are available so I can switch them to authenticated. E.g.:
AWS.config.region = 'us-west-2';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
region: 'us-west-2',
Logins: {}
});
const updateCredentials = () => {
const auth = store.getState().auth; //this gets the authentication credentials from a global store.
AWS.config.credentials.Logins = {
'cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXXXXXXX': auth.idToken
};
AWS.config.credentials.expired = true;
};
As best as I can tell, this is the correct way to do this. See the documentation at the bottom of this page, and here, and here.
However, my console shows that I have no authenticated users, so updateCredentials is not switching users to authenticated. What can be done to fix this?
at the end call AWS.config.credentials.get(()....)
Related
I am building an app (Main App) that authenticates through a seperate app (Auth App). I am able to signInWithCustomToken but the auth state does not persist between client browser refreshes even though onAuthStateChanged runs with the user after signInWithCustomToken.
Below is the authentication flow:
Users open the Main App, and click a button to open a popup that displays the Auth App.
window.open(AUTH_URL, 'window', 'width=400,height=600');
On the Auth App users create a Firebase account with email and
password.
firebase.auth().signInWithEmailAndPassword(email, password)
The Auth App makes a request to the Auth App's server to
generate a custom token.
// client side code
const idToken = await firebase.auth().currentUser.getIdToken()
const token = api.generateTokenAPIRequest(idToken);
// server side code
const generateTokenAPIRequest = (idToken) => {
const { uid } = await admin.auth().verifyIdToken(idToken);
return await admin.auth().createCustomToken(uid);
};
This custom token is passed back to the Main App via a postMessage.
window.opener.postMessage({ token }, APP_URL);
window.close();
When the Main App receives the authentication message, it signs the user in with the custom token.
window.onmessage = (e: MessageEvent) => {
if (e.origin !== AUTH_URL) { return; }
const { idToken } = e.data;
if (!idToken) return;
firebase.auth().signInWithCustomToken(idToken)
};
I listen to firebase.auth().onAuthStateChanged. This function runs correctly with the new user account but does NOT run again when I refresh the page. It is as if the user is not being stored in the Main App's storage.
It appears this was caused by an unrelated bug where firebase.auth().signOut() was being called unexpectedly. However I wanted to call out a few items in case someone stumbles upon this.
Make sure your service account is the same one you are using on your authenticated app. You can generate an Admin service account through the Firebase Console.
The post message approach has problems where a hacker can grab the ID token, sign in with it and then do all the actions the users can do. Make sure you check the origin of the post message like I do above. There are additional measures that can probably be put in place. I believe this is how the typical "Sign in with Google" works.
There are approaches with sessions that have been posted on Medium. Namely https://dev.to/johncarroll/how-to-share-firebase-authentication-across-subdomains-1ka8 and https://dev.to/brianburton/cross-domain-firebase-authentication-a-simple-approach-337k
Supporting this use case is currently an open issue https://github.com/firebase/firebase-js-sdk/issues/2303
Happy coding!
I have an Angular 10 site. I have an AWS Lambda (ASP.NET Core 3.1) that does authentication with AWS Cognito for users with email/password. But I want to allow users to also use Facebook (and eventually Google) to log in as well. In the Facebook/Google scenario my thought is not to allow access to AWS services directly for now (like S3, etc) but to interact with with my other lambda via a bearer token. I have a Cognito User Pool for which I created a Facebook identity provider and mappings. I read somewhere that I'd need an Identity Pool. So I created that and put in my Cognito user pool as a provider as well as Facebook.
Using the JavaScript code:
loginWithFacebook = () => {
const login$ = from(this.facebookService.login());
login$.subscribe(
(response: LoginResponse) => {
console.log(response);
this.facebookLoginToAWS(response);
},
error => {
console.error(error);
}
);
};
I can get a Facebook auth response no problem. Then using this code that you see on every blog, Stack Overflow post, and even in AWS's documentation (of course, substituting my own IdenityPoolId):
private facebookLoginToAWS = (facebookResponse: LoginResponse) => {
console.log('facebookLoginToAWS', facebookResponse);
if (facebookResponse.status === 'connected' && facebookResponse.authResponse) {
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
Logins: { 'graph.facebook.com': facebookResponse.authResponse.accessToken }
}, {
region: 'eu-west-1'
});
AWS.config.credentials.get((err) => {
if (err) {
return console.log("Error", err);
}
console.log("Cognito credentials", AWS.config.credentials);
console.log("Cognito Identity Id", AWS.config.credentials.identityId);
});
} else if (facebookResponse.status === 'not_authorized') {
document.getElementById('facebookStatus').innerHTML = 'Please log into this app.';
} else {
document.getElementById('facebookStatus').innerHTML = 'Please log into Facebook.';
}
};
I can get back a session token (in addition to a ton of other stuff like accesKeyId, identityId, and secretAccessKey).
But what do I do with that session token? Perhaps I'm confused, but I would think because there is a mapping between Facebook fields and Cognito fields, that somehow that Facebook user would be migrated into the Cognito user pool and I could get a JWT token for that user for my other lambdas. But after checking the AWS dashboard, I can see a log in (I think) in the Identity Pool but there is no corresponding User Pool entry.
Do I somehow have to manually migrate it over (using the AWS JavaScript SDK)? I don't want to use the Amplify library. Am I thinking about it wrong? Do I somehow use the session token as a bearer token with my other lambdas? Do I need to add that person as a user pool user somehow?
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.
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
So I have a basic webapp, and Im trying to create user access control using AWS cognito. I have a custom authorisation provider and upon entering correct username password. I do the following in my login page:
//Successful Login
var creds = AWS.config.credentials;
creds.params.IdentityId = output.identityId;
creds.params.Logins = {'cognito-identity.amazonaws.com': output.token};
//Store token in browser cache
localStorage.setItem('token', output.token);
localStorage.setItem('id', output.identityId);
//Launch dashboard
window.location = "./index.html";
Once I redirect the user to the dashboard, I trigger a onLoad function to check whether the user has correct login credentials and not expired ones. Using this:
//read browser cache
var id = localStorage.getItem('id');
var token = localStorage.getItem('token');
//validate session
AWS.config.region = 'ap-northeast-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: id,
Logins: {'cognito-identity.amazonaws.com': token}
});
//check if session is still active
if(AWS.config.credentials.expired) window.location = "./session-expire.html";
Problem is the expired property is always true. no matter what I do. How do you guys check if the credentials are valid?.
Thanks in advance,
Rajan
In the code samples you provided, you never acquire credentials. You set up the credentials provider, but do not call any services with it, or explicitly try to get the credentials.
Secondly, the credentials are not persisted across page loads.
This will lead to the credentials being expired in your code samples.
This question may help you, just replace facebook with your own developer auth flow. AWS.config.credentials are null between page requests