How do you get IAM credentials using Amplify Auth? - aws-amplify

I am trying to get IAM credentials using Amplify ^5.0.7. I am able to successfully log in and get OpenID tokens:
Auth.signIn({
username: user,
password: password
})
.then((u: CognitoUser) => {
console.log("User", u);
console.log("Credentials", Auth.Credentials);
console.log("Essential credentials", Auth.essentialCredentials(Auth.Credentials));
return Auth.currentSession();
}).then((data: CognitoUserSession) => {
console.log("Current session", data);
return Auth.essentialCredentials(Auth.currentCredentials())
}).then((c: ICredentials) => {
console.log("Credentials", c);
})
That successfully signs in, and gets a current session that has id, access, and refresh tokens. The problem is that there are no Credentials. currentCredentials() returns an ICredentials object, but its fields are all undefined. I think those should map to temporary IAM credentials.
The user I'm testing against belongs to exactly one group, and that group has an IAM role assigned to it.
Am I missing a step to request the temporary IAM credentials? I am not using amplify on the backend. I'm really using Amplify Auth because it deals with SRP for me.

I think I figured it out, and it was simpler than I thought. I want to post what I did to fix it, in case anybody else has the same problem.
What I did was basically correct, except you don't need to get the session first.
Auth.signIn({
username: userInput,
password: passwordInput
})
.then((u: CognitoUser) => {
console.log("Auth.credentials", Auth.Credentials)
return Auth.currentCredentials()
}).then((currentCredentials: ICredentials) => {
console.log("Essential credentials", Auth.essentialCredentials(currentCredentials))
})
There was not really a "code" trick to getting it to work. The real problem was that I was not careful enough about how I was setting up Cognito. To do this, you need both a user pool AND an identity pool. The identity pool points back toward the user pool. I had done that, but I wasn't setting Amplify up correctly. The way that works is:
Amplify.configure({
Auth: {
mandatorySignIn: true,
region: 'us-east-1',
userPoolId: 'us-east-1_CX1xxxxxx',
userPoolWebClientId: '5eo394ojo1gvdm6cbxxxxxxxxxs',
identityPoolId: 'us-east-1:f69259b2-ff84-4731-b80c-xf421-xxxx'
}
});
if you don't care about the IAM credentials, you don't seem to need the identity pool nor do you need to specify its ID. If you leave them out, you just get an empty Credentials back.
So Amplify does everything. I'm new to Amplify, and it's a lot less work then dealing with SRP directly.

Related

How to always get latest Firebase Auth Token

Currently I am using this code to get the latest auth token for firebase, which I use in a header with Apollo or URQL to query something else to be validated...
async getToken(): Promise<any> {
return await new Promise((resolve: any, reject: any) => {
this.afa.onAuthStateChanged((user: any) => {
if (user) {
user.getIdToken(true).then((token: string) => {
resolve(token);
}, (e: any) => reject(e));
}
});
});
}
I am use getIdToken(true) to always make sure I get a valid token since the token expires after one hour and the custom claims could be updated at some point.
However, my code gets a new token every time, when really I only need to get a new token when the old one is expired, or there is new information in the token's custom claim.
Should I be using some for of onIdTokenChanged() ? Does firebase store all this automatically in the firebase localstoreage db (IndexedDB), or should I be using some form of localstorage and calculating the expiry time ?
Basically, what is the best way to minimize the number of refreshes to the token to speed up my app instead of getting a new token every time?
Thanks,
J
Unless you are using a custom solution with the REST API, the firebase client modules will automatically refresh the auth token with the refresh token when the old one expires.
As for updating the custom claims, you will have to communicate with the client app through some means such as a server response if you invoke a cloud function or a realtime database listener that the user is subscribed to if you are updating it based on 'external' conditions.

Firebase service account to generate authentication token for client-side use with Google Apps Script

I am having difficulty using the FirebaseApp (a 3rd party API) to generate an authentication token that can be passed to a sidebar and used by the client to login and access my Firebase Database client-side.
I'm trying to use this tutorial but cannot get it working without using a database secret (which is being depreciated) in makeToken(). I'd prefer to use a service account as reflected in this tutorial. When I look at the difference between the tokens generated, the first 2 pieces separated by a '.' are identical, the last piece after the final '.' is different. The lengths are the same as well. eg:
//Example Generated by Database Secret: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2U=
//Example Generated by Service Account: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbml=
I can generate the OAuth access token, pass it to FirebaseApp and generate an authentication token, but when it is passed client-side and I attempt to authenticate I get an error: Login Failed! Error: INVALID_TOKEN: Failed to validate MAC.
It seems like there is a lot of misinformation and conflicting information on how this should be done.
I have a getFirebaseService() function server-side that uses Apps Script OAuth2 Library to get an access token.
function getFirebaseService() {
return OAuth2.createService('Firebase')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(fb_PRIVATE_KEY) //Service account private key
.setIssuer(fb_SERVICE_EMAIL) //Service account email
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scopes.
.setScope('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database');
}
I have a makeToken() function server-side that gets an authentication token from Firebase using the OAuth access token. I am able to use the service.getAccessToken() OAuth token server-side to access and store data. So that works, I guess my issue is creating a client auth token that's more restrictive.
function makeToken(){
var service = getFirebaseService();
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //Database Secret Works: "AAslhfi3MYACCESSTOKEN2930hf03ah4th8" but is being depreciated.
.createAuthToken(Session.getActiveUser().getEmail());
} else {
Logger.log("makeToken: " + service.getLastError());
}
}
Then client-side, from the sidebar, I try to authenticate with a custom auth token retrieved server-side from makeToken().
var userAuthToken;
google.script.run.withSuccessHandler(function (requestAuthToken) {
userAuthToken = authenticateClient(requestAuthToken)
}).makeToken();
function authenticateClient(userRequestToken) {
var ref = new Firebase(fb_URL);
ref.authWithCustomToken(userRequestToken, function (error, authData) {
if (error) {
console.log("FB Login Failed!", error); //Error below come from here.
}
else {
console.log("FB Login Succeeded!", authData);
}
});
return ref.authData.auth;
}
This results in Login Failed! Error: INVALID_TOKEN: Failed to validate MAC..
Edit: Is it possible FirebaseApp is incorrectly generating the JWT Authentication Token?
Edit2: I think the above edit is unlikely as I attempted to use the GSApp library and had the same issue. It only seems to want the depreciated database secret, not a service account OAuth.
Alright, so after a very long day I figured it out. I'm going to lay out what I ended up using for libraries and what the issue was (see the third library). The main problem was essentially that the tutorial was outdated and no a lot of people use Firebase in apps script.
OAuth2 (Server-side)
Link
I didn't have to change anything here! It was working fine and never an issue.
FirebaseApp (Server-side)
Link
This is a nice library and I stuck with it because it worked well (once I got it there). I had to make a change to my original code that came from the tutorial I mentioned. My code ended up like this and worked:
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //get OAuth Token
.createAuthToken(Session.getEffectiveUser().getEmail(), null, serviceAccount.client_email, serviceAccount.private_key);
//... Added the null, private key, and service email parameters.
Firebase (Client-side)
Link
Alright, so this is where my main issue was -- The tutorial I followed for client-side setup was old. I had to upgrade the code on my own to use the new 3.x version:
<script src="https://www.gstatic.com/firebasejs/5.8.2/firebase.js"></script>
// Initialize Firebase
var config = {
apiKey: "<Web API Key>",
authDomain: "<Project ID>.firebaseapp.com",
databaseURL: "https://<DB URL>.firebaseio.com/"
};
firebase.initializeApp(config);
With this firebase instance I was able to update my original authenticateClient() method:
function authenticateClient(userRequestToken) {
firebase.auth().signInWithCustomToken(userRequestToken).catch(function(error) {
// Handle Errors here.
console.error("authClient: ", error.code, error.message);
});
return {
uid: firebase.auth().currentUser.uid,
metadata: {
lastSignInTime: firebase.auth().currentUser.lastSignInTime
}
};
}
That's it! I now have a firebase instance with a signed in user via JWT Custom Token! I came across a few people with similar issues an I hope this helps.

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

Firebase Authentication : Lookup a user by Email

I am using Firebase Authentication with Email and Password
I would like to know if i can 'lookup' a user by email only while also not being signed in as a user
The reason I'd like to do this is, is to simply identify if I am already a user of the system, using an email address only
I looked at this older thread but it appears to be the previous version of Firebase
Is it possible to do this in the current Firebase, or my alternative would be to keep this information available (and open to all?) to find out if a given email is part of my system?
I use fetchProvidersForEmail(email)
and if the result return as empty array then, this email hasn't been use to sign up.
firebase.auth().fetchProvidersForEmail(email)
.then(providers => {
if (providers.length === 0) {
// this email hasn't signed up yet
} else {
// has signed up
}
});
You can look up user information by email:
firebase.auth().getUserByEmail(email)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
I'd like to make things more clear that this method does not exist — one could be looking for it in the firebase client library, in which it has never been available in the first place and it wouldn't be a good idea to have anyway. This method is part of the admin SDK, so in order to call the method, you need to run it on the server, and invoke it from the client. OP didn't scope the question to firebase client library, so my answer is still correct.
Retrieve user data
The new method of creating users with email password returns a value whether the given email address is already in use. See here
import { fetchSignInMethodsForEmail } from 'firebase/auth'
fetchSignInMethodsForEmail(auth, registerEamil).then((result) =>{
console.log("result", result);
if (result.length === 0) {
Navigate("/authentication/select_role/" +
registerEamil)
} else {
Navigate('/')
}
Server side option:
https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects.accounts/lookup
POST https://identitytoolkit.googleapis.com/v1/projects/{targetProjectId}/accounts:lookup
{ "email": ["rodneydangerfield#stackoverflow.kom"] }
As for today 2021 Nov. 18th, there is no way provided by the Firebase SDK to fetch a user by email.

Firebase v3 GoogleAuthProvider not saving email

(1) Question:
Is there a way to read user email having the uid? (permitted only for super user or server)
Ps.: I don't want to save in the Realtime database, because even though only the current user can change it, he can erase or put some fake email..
(2) Problem:
I'm trying to retrieve user email with GoogleAuthProvider in Firebase v3
Thats the code I'm using:
signInWithGoogle(): Promise<any> {
let provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("https://www.googleapis.com/auth/userinfo.email");
return firebase.auth().signInWithPopup(provider)
.then((result) => {
console.log(result.user.email);
console.log(result.credential);
let token = result.credential.accessToken;
return this.createOrUpdateUser(result.user, token);
});
}
The result:
result.user.email # null
result.user.providerData[0].email # correct_email#gmail.com
Even though the email is in the providerData, it is not attached to the auth..
Is it a firebase bug or can I fix it somehow?
Thanks!
The 3.2.0 web sdk launched yesterday should automatically ask for the profile scope when signInWithPopup for google provider. Try deleting the test user and sign in again. The top level email should be populated.

Resources