Is there a better way of implementing this without multiple fetch request? - javascript

This is login component from a react application and I am trying to do a simple authentication login with Firebase as my real time database.
My first approach is to execute a fetch request (GET) to see if there is any existing user. Afterwhich, if the password do match with the user, I want to update the "isLoggedIn" field to true. In order to achieve the following, another fetch request (PATCH) was made.
Therefore, I am wondering if it is a bad practice to have multiple fetch request in one function and would like to know if there is a more efficient or better way of implementing this simple application?
const loginHandler = async (userName, password) => {
const url = `insert_url_link_here/users/${userName}.json`;
try {
const response = await fetch(url);
const user = await response.json();
if (user.password === password) {
await fetch(url, {
method: "PATCH",
body: JSON.stringify({ isLoggedIn: true }),
});
}
} catch (error) {
console.log(error);
}
This is the schema of my database.
--users:
eric:
isLoggedIn: false
password: "321"
test:
isLoggedIn: false
password: "222"

You're accessing the Firebase Realtime Database through its REST API, which implements simple CRUD operations, and there's no way to combine a read and a write operation into a single operation there.
The only other relevant operation is the conditional write, which you can use to only write data if it hasn't been modified since you read it. You can use this to implement an optimistic compare-and-set like transaction mechanism, but given that you're toggling a boolean it doesn't seem to apply here.
If you're on an environment that supports it, you may want to consider using the Firebase SDK for that platform, as the SDKs typically use a web socket to communicate with the server, which often ends up being a lot more efficient than performing multiple individual HTTP requests.

I suggest using a Firebase client implementation and looking at the Firebase documentation. There are well described and explicit examples in various languages:
https://firebase.google.com/docs/auth/web/start
Besides, I don't see the point of storing passwords or login state on your side since Firebase Auth already does this for you in a secure way.

Related

Watermelon Sync With Firebase

I'm wanting to use watermelon sync with Firestore but I'm not getting it. I do not even know where to begin with. I'm not using API. I want to do it only in React Native. I want to sync my app offline and online. Can someone help me??
Im using React Native to do That...
import { synchronize } from '#nozbe/watermelondb/sync'
async function mySync() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
const urlParams = `last_pulled_at=${lastPulledAt}&schema_version=${schemaVersion}&migration=${encodeURIComponent(JSON.stringify(migration))}`
const response = await fetch(`https://my.backend/sync?${urlParams}`)
if (!response.ok) {
throw new Error(await response.text())
}
const { changes, timestamp } = await response.json()
return { changes, timestamp }
},
pushChanges: async ({ changes, lastPulledAt }) => {
const response = await fetch(`https://my.backend/sync?last_pulled_at=${lastPulledAt}`, {
method: 'POST',
body: JSON.stringify(changes)
})
if (!response.ok) {
throw new Error(await response.text())
}
},
migrationsEnabledAtVersion: 1,
})
}
the example above is the code shown on the Watermelon website. But I want to do it without using API! Only with React Native and Firestore/firebase. How could I do this in react Native, and whenever there is any change in the application it automatically saves it in the database when the user is connected to the internet? My app is Offline Frist
There are some packages that helps with syncing react native to firebase and you won't need to build your own backend server and apis. To quote from the watermelondb docs.
MelonFire, a React Native library to sync your database to Firestore. MelonFire overcomes common bugs in implementations (e.g. timestamp jitter, multiple writers, Firestore's 500-write transaction limit, retries) to guarantee database consistency.
Firemelon, an alternative implementation to sync your database to Firestore. It relies on changes being smaller than Firestore's 500-write transaction limit, and doesn't handle server timestamp intricacies, but supports ignoring certain tables when backing up.
Hope this helps.

onError Authentication with refresh token

In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.

Firebase HTTPS callable function context.auth is always null when used with custom token auth

HTTPS callable function is called directly from our app after signing in using custom token (custom auth), but context.auth is null in function eventually.
I am wondering if this is something expected? I am not providing any specific example (our client is using Firebase SDK with Kotlin, everything is implemented accordingly to the example in docs), just want to know if maybe someone had similar issue or maybe we need to double check our client's code (custom token authentication is actually working there, since we use firestore with security rules that require it).
I was trying to find some information about certain restrictions, but there's none: Firebase FAQ https://firebase.google.com/support/troubleshooter/functions/auth/callable (nothing about custom token), this answer here Do I need to use verifyIdToken on the context.auth object in firebase cloud functions?
Been asked to add an example of the cloud function, nothing specific, is reproducible with simple one like the following (auth will be always null in log record):
exports.getData = functions.https.onCall((data, context) => {
functions.logger.info('Auth info', { auth: context.auth });
return {
success: true,
data: null,
};
});
Seems like a potential race condition, Ensure that Auth has created the user object before requesting the callable function if you are calling it directly after a sign-in method.
This can be done using a callback from an onAuthStateChanged.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
Source: https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user

How to verify user email while using GraphQl and Node.js

I'm trying to implement user email verification in my pet project with GraphQL and Node.js.
I already have signUp resolver which sends verification token but I've just understood that when a user clicks a link there is no way to send data from an email to the next GraphQL resolver which would use the token and verify the email.
So the question is: shall I make REST endpoint /verify to do the work or there is a way to use /graphql endpoint
If you use a separate /verify endpoint, you'll most likely want to also redirect the user back to your site after processing the request. One approach would be to effectively reverse this flow, linking to your website and then having your page make the necessary GraphQL request.
Alternatively, you can invoke your verify resolver through a link in the email. express-graphql will handle both POST and GET requests. There's a couple of things to keep in mind with this approach though:
It will only work with queries, so your "verify" field will need to be on the Query type
The request will work in a browser context, but will flat out fail if you call it from inside, for example, GraphiQL
Here's a basic example:
const typeDefs = `
type Query {
verify: Boolean # Can be any nullable scalar
}
`
const resolvers = {
Query: {
verify: (root, args, ctx) => {
// Your verification logic
ctx.res.redirect('https://www.google.com')
}
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
app.use('/graphql', graphqlHTTP((req, res) => ({
schema: MyGraphQLSchema,
graphiql: false,
// Inject the response object into the context
context: { req, res },
})))
app.listen(4000)
You can then just navigate to this url in your browser:
http://localhost:4000/graphql?query={verify}

Flow for authentication when MFA required for user in AWS Cognito

I am attempting to add MFA for user authentication to an already existing solution (built in Angular) for device management within AWS Cognito.
I am having trouble figuring out how to handle this particular response well from a user-experience perspective. It actually feels broken, so would love if anyone else has experience pain points here.
See Use Case 23. for example implementation, mine is below:
authenticate(username: string, password: string): Observable<any> {
// init cognitoUser here
return new Observable((observer) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result: any) => {},
onFailure: (err: Error) => {},
mfaRequired: (codeDeliveryDetails: any) => {
// SMS has just been sent automatically
// and it needs to be confirmed within this scope
// The example linked requests the code via `confirm()`
// which is awful UX...and since this is a service
// probably non-compliant with best practice
// However, without this `confirm` at this point in
// time, we have no confirmationCode below
cognitoUser.sendMFACode(confirmationCode, {
onSuccess: (result) => {
observer.next(result);
observer.complete();
}, onFailure: (err: Error) => {
observer.error(err);
observer.complete();
}
});
}
});
});
}
Expected:
If the user authenticates successfully but has not added this device through MFA, we can manage the redirect to appropriate confirmation code form page and trigger the sendMFACode function manually (perhaps through some sort of limited session?)
Issue/s:
we don't have a session, so we have no way of asking the user the MFA code sent automatically outside of this login screen...catch 22?
adding another show/hide field in the login form doesn't work as it would hit the sendMfaCode function multiple times, resulting in multiple SMS codes sent.
Has anyone had any luck stepping out of this flow?
Whilst I’m sure very talented people worked on the amazon-cognito-identity-js API, it is just straight up badly designed. Thus why it’s been depricated. My personal advise would be to migrate to Amplify, which makes me much less angry.
With Amplify you can do these ones.

import Amplify from 'aws-amplify'
import Auth from '#aws-amplify/auth'
let mfaRequired = false
Amplify.configure({
Auth: {
userPoolWebClientId: '',
userPoolId: ''
}
})
const logUserIn = (user) => {
// Go forth and be happy
}
// Run me on your login form's submit event
const login = async (username, password) => {
const user = await Auth.signIn(username, password)
if (user.challengeName === 'SMS_MFA') {
// Change UI to show MFA Code input
mfaRequired = true
return
}
return logUserIn(user)
}
// Run me when the user submits theire MFA code
const senfMfaCode = async (mfaCode) => {
const user = await Auth.confirmSignIn(mfaCode)
return logUserIn(user)
}
BUT if for some sad reason you need to keep using amazon-cognito-identity-js don’t worry. I got you.
Just keep the cognitoUser object stored outside the callback. The documentation is a little misleading because it only show’s self contained examples but there’s no reason that you can’t notify your UI when MFA is required and then call cognitoUser.sendMFACode() later.
Just remember that the documentation show’s the passing of this to sendMFACode() for scoping (which is terrible) but you can just declare your callbacks as a variable and share it between your authenticateUser() and sendMFACode() functions (or as many functions as you like).
import { CognitoUserPool, AuthenticationDetails, CognitoUser } from 'amazon-cognito-identity-js'
export let mfaRequired = false
export let cognitoUser = null
export const cognitoCallbacks = {
mfaRequired () {
// Implement you functionality to show UI for MFA form
mfaRequired = true
},
onSuccess (response) {
// Dance for joy the code gods be glorious.
},
onFailure () {
// Cry.
}
}
export const logUserIn = payload => {
cognitoUser = new CognitoUser({
Username: 'Matt Damon',
Pool: new CognitoUserPool({
UserPoolId: '',
ClientId: ''
})
})
return cognitoUser.authenticateUser(new AuthenticationDetails(payload), cognitoCallbacks)
}
export const sendMfaCode = MFACode => {
cognitoUser.sendMFACode(MFACode, cognitoCallbacks)
}
That’s a super basic implementation and on top of that you could,
Just overwrite the mfaRequired function in an external module to do whatever you want.
Wrap the whole thing in a pub/sub plugin and subscribe to events.
Hope that helps!
I know this is an old question, but I thought this answer might be helpful for anyone who is still using the amazon-cognito-identity-js API instead of Amplify. #stwilz's answer works somewhat, but there are a few complications that come when you stray too far away from the documentation's use cases (and might come about when doing TOTP MFA instead of SMS MFA). I've created a workaround to address situations where you might get errors like Invalid Access Token, Missing parameter Session, or Invalid session for the user.
If you need to use something like sendMFACodeoutside of the callbacks, it's not enough to just keep cognitoUser stored outside the callback. You actually have to call the authenticateUser function again, then call the sendMFACode within the callback. It gets more complicated with verifySoftwareToken for TOTP, where you actually have to store the Cognito user object and then reassign it when calling authenticateUser again.
If none of this makes sense, I've created a simple Github Gist that uses React and amazon-cognito-identity-js to show how such a flow would work. It's here: https://gist.github.com/harve27/807597824720d0919476c0262e30f587

Categories

Resources