How do I allow a role insert into a table (for signup), but after that deny further inserts for that user? - hasura

That's my question. I am using Hasura, and defining 'user' permissions.
Users are of course allowed to modify their own information, and not allowed to insert new records into my users table.
But when they signup, they should be allowed to insert themselves. So how can I define this permission?
To make my scenario more clear:
I have a React app, that uses an external OpenID provider. So a new user signs up there, and the provider returns a JWT to my app, containing a user I've never seen before.
My app does not know that, it just uses the access token to send to the Hasura backend to retrieve further info about this user, using the 'user' role. But it uses a query which will automatically insert the user if not found.

There's really not a safe way to allow sign-ups without involving a backend service. It is a very bad idea to allow anonymous inserts into your user table, even if you added a unique constraint against a user ID or email address.
If you have the option of using NextJS, see the Hasura example for configuring NextAuth. This works by configuring your app with a protected API route that uses your Hasura app's ADMIN_SECRET to insert new users who have authenticated with a third-party.
If NextJS isn't an option, Hasura's Auth0 example similarly uses a callback method to insert an authenticated user if they don't exist.

In the user table, for the user role, you need to add a permission with custom check. And the check should be user_id equals x-hasura-user-id.
{"id":{"_eq":"x-hasura-user-id"}}

For non-logged-in users, leverage the anonymous role by setting the permissions that make sense for your use case: https://hasura.io/docs/1.0/graphql/manual/auth/authorization/common-roles-auth-examples.html#anonymous-not-logged-in-users
Edit after the comment:
Ah, I see. When the user comes to your app, your app goes and retrieves some data that it expects every user should have (for example perhaps the user info store on the user table). But since it's a new user, this info is not there.
At this point, your React app knows that:
there's someone with a legitimately signed JWT cookie (use a library to verify the signature) and
there's no user info from the backend. Therefore, the React app shows
a "Welcome new user, wait while we're setting up your account".
Then
the React app makes a mutation to a signup Hasura action you'll
prepare. Once that returns, you proceed as usually (redirect the user to their home page).

use hasura action handler instead. Inside your handler, do a check if the user already exists or not. If not then insert a new row.

Related

Synchronize users created with Firebase Auth to my custom backend

I want to use Firebase Auth for my user login/registration process. Everything else should be handled by my own backend (spring boot app + postgres db).
Now I'm asking myself how I can synchronize a new created user to my user table in postgres. I thought about the following:
REST call through client - Everytime I get a success event from the firebase sdk I call an additional request to my backend which sends uid, username etc.
Problem: What if my backend call fails but the register process was successful ? That would lead to an inconsistent state since (at least thats what I understanded) I can't easily rollback. That would lead to situations where a user can login into my app without my backend knowing the user. This would crash/ invalidate all my following queries (e.g. search after user xyz would lead to no result even though he/she exists)
Check the existence of the user in the postgres database
Here I would query the uid from the database (which I got from the jwt) and create a new user if it doesn't exists in every incoming request.
Problem: The user query is a unnessecary overhead for every incoming request.
Trigger with cloud functions - When I understood it right firebase auth is firing events when a new user is created in cloud functions. This could be used to make the external api call.
Problem: I dont know what happens when my external rest call fails at this point. Can I rollback the registration ? Will I be ever catch this event again ? I also proably would have an eventual consistency situation, since I dont know when the cloud function triggers. Furthermore I would prefer not to include cloud functions to my stack
Is there any way how I could do this in a transactional manner ? Did anyone else tried is using sth simular ?
Thanks for every help!
The easiest way is actually to not synchronize auth data, but instead decode and verify the ID token of the user in your backend code.
This operation is (by design) stateless, although Firebase's own backend services often implement a cache of recently decoded tokens to speed up future calls with the same ID token.
Apparently, I finally came up with a different solution:
Register user per Firebase SDK (e.g. with email + pw method)
Make a post-call to my own registration api including the resulting uid from the previous step and some metadata
API creates a new user including a column with the UID + Fetches the firebase token of the user and adds an internal claim that references to the internal Postgres UUID via Admin SDK.
Frontend gets the created user and hard refreshes (very important, since the previously fetched token won't contain the newly added claim !) the firebase token and verifies that it contains the token. If it does -> everything is cool, if not some oopsie happened :) That will require a request retry.
Later when you start your app you can just check if the passed token contains the custom claim, if not open the sign up/sign in page.
Every endpoint except the one for registration should check if the claim is set. If not just forbid the request.
How to set custom claims:
https://firebase.google.com/docs/auth/admin/custom-claims#set_and_validate_custom_user_claims_via_the_admin_sdk
You can use the Firebase Admin SDK to create the user account from your back-end instead of from the client.
So first you create the user in your database, then grab the ID and use it to create a user with the same ID in Firebase.
If all goes well, send a confirmation to the client and sign it in using the same credentials they entered.
Why not creating an endpoint in your backend service and call this endpoint when a client side authentication succeeds?
This method should do 2 things:
decode token to get access to Firebase user object (Firebase Admin)
Compare Firebase user with your internal user table. if it doesn't exist you can create it using firebase user object, otherwise do nothing.
This solution allows you to do other nice things as well (Syncing user info between Firebase and your internal db, providing a way to let a frontend know if this user is new or not, ...) at a relative small cost (1 get call per sign in)

Have one user signup another user with custom fields in firebase/flutter

I am trying to determine if the following scenario is possible with flutter and firebase:
we have users within the company who will be given access to the app, where on the homepage will be a signup another user button where they enter in that user's email and password, they get signed up, and then the original user specifies custom fields for the 2nd user, such as company name, role, position, etc.
Is this possible with flutter and firebase?
Have asked the flutter google group and was told about custom authentications, but from what I see that is just an external authentication system and doesn't show me how to let one user create another users profile with fields.
Any ideas?
The first thing to consider is whether those properties need to be in the user profile at all. The user profile is sent with every request, and should only contain information that is relevant for securing access. If you have additional information to store about the user, you should store it elsewhere (such as in one of Firebase's databases) using the UID of each user as its key.
Assuming that the information is about security (such as the role seems to be, there is no secure way to let one user set security properties (typically referred to as claims) from client-side code. As soon as this is allowed from client-side code, anyone could set such properties for anyone else. That's why setting custom claims for a user is only possible with Firebase's Admin SDKs, which are designed to run in a trusted environment - such as your development machine, a server you control, or Cloud Functions.
There are a few other options, but it's important to realize they're all implemented on top of the above approach.
There is an experimental extension that allows you to set auth claims by writing a document into Firestore, which something like this (JavaScript syntax, but the Flutter code will be similar):
db.collection("user_claims")
.doc("abc123")
.set({
role: "admin",
groups: ["example1", "example2"],
});
Now of course you'll want to make sure that you secure writing to the user_claims collection, as otherwise you'll end up with the same security risk I mentioned in the first paragraph, where everyone can claim any role they want.
Alternatively you can write your own server-side API (for example on Cloud Functions) that you expose to your application, and that then calls the Admin SDK. Here too, it is important to secure access to this API, to ensure only authorized users can call it.

Firebase subaccounts

I'm currently building a POS/Store admin app, when a user gets into my app, the Owner of the store will then be asked to login only once for setup purpose (e.g. a new machine), the app will then display a list of staffsName that has already been added by this owner, and then everytime a staff wants to start a new transaction, he/she will only need to click on his/her name, then enter his/her 4-digit pincode to 'login' and process the transaction.
How do i go about achieving this?
I was thinking of using firebase auth for the 'login' of the staff, but then when i log in using the staff credential, I will lose access to the uid of the owner, which is needed to access the owner's store data such as his/her products.
i'm also thinking of using firestore to store the 4digit pincode, but then i'm also concerned about security
There are multiple ways you can approach this, one where you utilize the email login by simply appending a fake domain to the username to create a valid email domain. This user account could be the designated 'user' in question, or utilize credentials inside custom claims or hidden in a database that allows the client or server (depending on your preference) to then log in as the user.
Moreover if you want the manager to login once you can add Authentication State Persistence to specify whether a signed in user should be indefinitely persisted until explicit sing out, page reload etc.
Another approach requires the user also to have a valid auth that is not an email password and you link your pin auth to that main account with Firebase Auth Linking per the documentation: https://firebase.google.com/docs/auth/web/account-linking.
This does however require that the user be registered from an auth provider such as Google, Twitter, Apple, etc. then the user will have to activate this link by logging in for authentication purposes. This can in theory be automatically generated and processed without the user knowing.
It is a long way around it since this is essentially a custom solution but it does create the flow you are looking for without having to use a custom auth provider.

How many time Anonymous users are created in Firebase

I would like to use the signInAnonymously feature from firebase but I'd like to know how it works exactly.
Is this anonymous user added as any other user in Firebase (but without an email or password) ?
Is it automatically removed after a certain time ?
If I call this method on each app launch. Will it create a new user everytime ? How will it know how te reuse an old one ? Local Storage ?
So will it create another user in database if my users connects from another device ?
Thanks a lot
An anonymous user account is similar to any other user account in Firebase, except that there is no way to identify the user.
Anonymous users are not automatically removed.
Each time you call the API to create an anonymous user, a new account will be created. To re-use the existing account, you should listen for the auth state when the app starts and only create a new user account if the user isn't signed in yet.
If the same (anonymous) user signs in from multiple devices, they will get a separate account on each. If that is not what you want for your use-case, you should require one of the identified authentication methods (email/password, Facebook, Google, etc)/
Frank's answer to the third question is incorrect.
From the documentation:
If there is already an anonymous user signed in, that user will be returned instead. If there is any other existing user signed in, that user will be signed out.
Therefore, this method can be safely called every time the application starts.

How to set the Principal in an ASP.Net app

I am writing a web app for a client. Users will have a one-time key that they will use to initially identify themselves to the app. Once the app verifies that the key is valid it will take them to a page where they can create a normal account to use for all subsequent logins. The create-account page should only be accessible after entering the key and shouldn't be accessible otherwise. I.e, it shouldn't be accessible to users logged in with a normal account.
This is asp.net 3.0 using a custom membership provider.
My plan is to create a temporary account based on the key and authenticate the user with that account. This allows them access to the create-user page (which is protected with a location tag ) where they can create the formal account. I then authenticate them with their new account and delete the temporary account.
The flow is: the user goes to a page where they enter the key. If the key is valid I create the temporary account, call FormsAuthentication.SetAuthCookie, and redirect to the create-account page. This all works, although it seems a little complicated.
The problem is that the create-user page is available to any authenticated user; I only want it available during the time between entering the key and creating the formal account. So I thought I'd create a special role for the temporary account and make the create-user page accessible only to that role and none other. I created my own Principal object with a special role and tried setting it when I authenticate the temporary account but I can't get that to work.
I'm really hoping I don't have to write a custom role provider just to do this.
How can I make this work? There's gotta be a simpler way!
Why not simply create the real account when they enter the key. Assign it some random name and then let them change the name and other details. Then you don't need the create user page, just the enter key page and an account details editing page. If you're concerned about getting the account details filled in, you could set it up (perhaps via code on a MasterPage) so that incomplete accounts always get redirected to the edit details page until the details are entered.
Or, you could have them enter the required details in addition to the key code on the enter key page and simply use those details when creating the account.
My advice would be to avoid the use of temporary accounts when validating the user. Instead, generate your own logic for validating the sign-up key. Then, at the head of the page, you can check whether the user is an authenticated user (SetAuthCookie has been called) and jump to a different page if this is true.
You may even be able to change the page access to forbid this page to authenticated users (I know you can disable accounts for unauthenticated users but I'm not sure if you can go the other direction).
The key, though, is to avoid relying on the membership provider when, in fact, the user is not yet a member!
Assign an "incomplete" role when authenticating against the temporary token, then restrict access to only that role... when the account is created, send them to a re-login page (terminating the authentication token). This will simplify your security model.

Resources