How to identify app users data in the backend?

My users are able to create data once they signed in in my app with their Internet Identity. I want to set rules on the backend side to allow them to only edit their own data.

“user A can edit his/her data, user B can edit his/her data, user A cannot edit user B data”.


  1. On the client side, the HttpAgent has to be created with the signed in user identity or is it optional`?

const agent = new HttpAgent() // as generated by the sdk

or

const agent = new HttpAgent({identity}) // identity is the one of the signed in user through authclient


  1. On the backend (Motoko) side, is it correct to say that the function’s caller is the identification of the end user (the user who use the app and signed in with the internet identity)?
public shared({ caller }) func who: async() {
    // the caller == the end user?
}

  1. How should I save the data with the end user information?
public shared({ caller }) func set(obj : MyObj): async() {
    // TODO: save obj with caller
}

I was trying to set the principal.toText in my object on the client side and comparing it in the backend but, it does no work out.

Client:

await set({...myObject, user: authClient.getIdentity().getPrincipal().toText()}); 

Backend:

public shared({ caller }) func set(obj : MyObj): async() {
    if (Principal.toText(caller) != obj.user) {
       // => Always true => does not work for my use case
    }
}

I am not that successful, therefore would appreciate any help to solve some these questions.
Thanks in advance.

1 Like

I guess I can answer questions 2. and 3. with the linkedup example.

  1. yes most probably the shared({ caller }) is the signed in user

  2. the (all) user should be save within the data in the collection and it should not be provided by the frontend but only handled on the backend side. the entry point for such statement is the function hasAccess of the linkedup example.

In Motoko, caller will be the principal of the immediate caller of the shared function - so in a recursive call from the canister, it will be the principal of the calling canister, and in a external call from the frontend, it should (I believe) be the principal of the user (authenticated or anonymous).

If you haven’t seen already read it, maybe this would help Integrating with Internet Identity | Kyle Peacock's website.

(I’ve alerted the II team to your query…)

2 Likes

Thanks Claudio!

Glad to hear your feedback about the caller. I hope you are right.

Regarding Kyle’s blog post, I had a look, but, I am confused about my 1. question because the code displayed in the article does not match the one automatically generated by the SDK.

In the article, the {identiy} is provided to create an httpAgent but, in the automatic code, the agent is generated without (see createActor and related comment).

export const createActor = (canisterId, options) => {
    const agent = new HttpAgent({...options?.agentOptions});

   ...

/**
 * A ready-to-use agent for the decks canister
 * @type {import("@dfinity/agent").ActorSubclass<import("./decks.did.js")._SERVICE>}
 */
export const decks = createActor(canisterId);

Maybe I should just understand the automatic generated code as a sample one and not the proper actor to be used.

I believe the argument is optional, and will default to using the anonymous identity when omitted.

It confirms the blog post is correct, the identity of the signed in users need to be provided to the http agent and the createActor in the automatically generated code should be considered as a sample code.

I’ll update the blog post and example code to 0.8.0 today. I’d been focused on official doc updates over my blog

Awesome! Looking forward to it.

I gave a try to the your updated auth-client-demo for v0.8.0 but did not manage to pass the local sign-in.

The local anonymous identity provider redirect to the Internet Identity but, after successful authentication, it does not redirect to localhost:8000.

I face the exact same problem with my app too.

Something is buggy or is missing in your demo?

git clone GitHub - krpeacock/auth-client-demo: Example demo of how to use https://www.npmjs.com/package/@dfinity/auth-client to make authenticated calls to an IC app
cd auth-client-demo
npm ci && npm i @dfinity/identity @dfinity/candid @dfinity/auth-client @dfinity/authentication --save-dev
dfx start --background
dfx deploy
find local internet identity canister id in .dfx/local/wallets.json
update webpack.config.js with this canister id for LOCAL_II_CANISTER
dfx deploy
open browser localhost:8000?canisterId=web_app_canister_id
click sign in
open new tab localhost:8000?canisterId=local_idenity_canister_id
click “Authenticate”
redirect to identity.ic0.app
click “Authenticate” and process
=> no redirect to localhost:8000!

Oh, I bet it’s the II URL - do you have the #authorize hash at the end?

Yes. As I said I tried with your auth-client-demo and did not change the url beside updating the canister id with the one I found locally in wallets.json.

Note that there is a redirect when I access the anonymous local identify provider.

When I click “Log in” in your app, it does open a new tab with http://localhost:8000?canisterId=...#authorize but, it is shortly redirected to http://localhost:8000/authorize?canisterId=..., not sure that’s the expected behavior.

Yeah, that redirect seems weird to me. If you have chrome available, try going to http://<canister-id>.localhost:8000#authorize

Oh I think I get it. It probably doesn’t work because I did not had deployed a local internet identity. I thought it was already deployed. Anyway, I think we can therefore consider the topic as closed, thanks for the support.

Regarding my original questions, summarized and tested:

  1. yes the identity of the sign in users (the users of the web app) has to be use to instantiate a new HttpAgent({identity})

see my function ic.utils.ts

  1. yes it is correct to say that the function’s caller is the identification of the end user (the signed in user of the web app)

  2. the data can be saved as in linkedup. Next to the data, the user principal can be saved. Upon any request, the corresponding element should be read from the collection, for example a Trie, and both msg.caller and saved user can be compared to check if it matches or not.

See the function getDeck in my implementation.

To with my user identity I signed in in my web app and created a data. On my friend’s laptop, with his internet identity, we tried to fetch the data which was rejected by the check I have build. Therefore it practically validated that the user identity on the data level was valid :partying_face:.

Thank you for your answers and help!

3 Likes