AWS Cognito login with Facebook - javascript

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?

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

aws amplify javascript invalidate token on signOut

I am using javascript version of aws amplify in my reactjs application.
https://github.com/aws-amplify/amplify-js
When I call signOut method as mentioned in the document below:
https://github.com/aws-amplify/amplify-js/blob/a047ce73/packages/aws-amplify-react/src/Auth/SignOut.tsx#L109
code:
import { Auth } from 'aws-amplify';
async function signOut() {
try {
await Auth.signOut();
} catch (error) {
console.log('error signing out: ', error);
}
}
It just clears data from local storage and cookies. I want it to invalidate id token as well as access token. These tokens can still be used to access AppSync or API Gateway. We can use await Auth.signOut({global: true}); But there is a difference between these 2 methods. The former is used to signOut from the current session, but the other is used to signOut users from all the devices.
I read about calling invalidateTokens(true) here: https://github.com/aws-amplify/aws-sdk-android/pull/2415
Is this available in javascript version of aws-amplify? Please let me know any alternate solution to invalidate tokens on Auth.signOut() call.
thanks
As documented in the amplify documentation,
https://docs.amplify.aws/lib/auth/emailpassword/q/platform/react-native/#sign-out
Amazon Cognito now supports token revocation, and Amplify (from version 4.1.0) will revoke Amazon Cognito tokens if the application is online. This means the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens.
You need to use the RevokeToken function to invalidate the token as documented in this one
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#revokeToken-property
var params = {
ClientId: 'STRING_VALUE', /* required */
Token: 'STRING_VALUE', /* required */
ClientSecret: 'STRING_VALUE'
};
cognitoidentityserviceprovider.revokeToken(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Another option is to use the revoke endpoint.
https://docs.aws.amazon.com/cognito/latest/developerguide/revocation-endpoint.html

How to verify a user's ID token in firestore using Javascript?

I am building a react native application and am using Firebase, more specifically firestore, in order to manage my data. My current objective is to implement an auto login feature on my app, where if the user exits the app, I want them to stay signed in, unless they manually hit the Sign Out button before exiting the app. Here is my current process of doing this:
When the user logs into the app, I sign them in by:
firebase.auth().signInWithEmailAndPassword(email, password).
I then get their idToken by:
let authIdToken = "";
firebase
.auth()
.currentUser.getIdToken(true)
.then(function (idToken) {
authIdToken = idToken
})
.catch(function (error) {
console.log(error)
});
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity. If it is valid, then I can log the user in using their idToken. In react native, I can do this by doing:
AsyncStorage.setItem(
"userData",
JSON.stringify({
token: token,
})
);
Now when the app loads up:
const startScreen = props => {
useEffect(() => {
const tryLogin = async () => {
const userData = await AsyncStorage.getItem("userData");
const transformedData = JSON.parse(userData);
const { token } = transformedData;
await firebase
.auth()
.verifyIdToken(token, true)
.then((payload) => {
console.log(true)
})
.catch((error) => {
if (error.code == "auth/id-token-revoked") {
// Token has been revoked. Inform the user to reauthenticate or signOut() the user.
console.log("revoked")
} else {
console.log("error")
}
});
};
tryLogin();
}, []);
The Issue: When I try to verify the token this way, I am met with the following error: firebase.auth().verifyIdToken is not a function.
I read through the documentation and am unsure of how else to verify this token using JS. How do I verify it? Let me know if my verification process is incorrect and how it should be done. I am new to using firestore and doing authentication in general and hope to learn how to do it the right way.
Another helpful note: This is how I am configuring my firestore: !firebase.apps.length ? firebase.initializeApp(firebaseConfig) : {};
Thanks!
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity.
This is completely unnecessary. Firebase Auth with persist the signed in user, and automatically refresh the token without you having to do anything. All you need to do is listen to when updates to the token are made available, and act on the new token as needed. You can establish an ID token listener using onIdTokenChanged as shown in the linked API documentation:
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
}
});
Once you have this token, you know that the user is successfully signed in. There is nothing left to do. There is no need to use it to sign in.
Also, you can't verify the token on the frontend. The verifyIdToken method you're looking at is for the Admin SDK only, which only runs on the backend. The idea is that you get the token on the fronend, then pass it to the backend as described in the documentation for the Admin SDK. The backend uses this to securely determine if the user on the frontend is who they say they are.
Since you didn't say if you have a backend or not, dealing with this token might not be necessary at all. If you just want to know when the user is signed in (even if they are just returning to the page after being away, then you can skip everything above and just use an auth state observer. Again, Firebase Auth persists information about the user so you don't have to sign them in again. The observer will tell you when the automatic sign-in is complete, or if they are not signed in at all.

How to persist Cognito User Session

I am using javascript sdk for AWS cognito and able to login with aws cognito and receiving tokens in response.
I can see that the user session is valid until I refresh the page. Please suggest how the user session can persist after refreshing the page.
Below is my code.
function getSession() {
let poolData = {
UserPoolId: _config.cognito.userPoolId, // Your user pool id here
ClientId: _config.cognito.clientId, // Your client id here
};
//alert(sessionStorage.getItem("SessionName"));
let userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
cognitoUser = userPool.getCurrentUser();
cognitoUser.getSession(function (err, session) {
if (err) {
alert(err);
return;
}
console.log('session validity: ' + session.isValid());
//Set the profile info
cognitoUser.getUserAttributes(function (err, result) {
if (err) {
console.log(err);
return;
}
console.log("------>>" + result);
//document.getElementById("email_value").innerHTML = result[2].getValue();
});
});
}
good news - the SDK does this for you. Check out their code for the getsession method
You can see they store the tokens to local storage for you.
To view the tokens from Google Chrome, go to developer tools -> Application. You should see a 'Storage' section on the left hand side. Open Local Storage, the tokens are saved under the URL of the application.
You should not need to access these token directly, the SDK will fetch and save the tokens as required when you call different methods.

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.

Categories