Improve Internet Identity to make it’s users able to disclose their principal from one service to another

The problem

Internet Identity is great.
It is secure, it is easy to integrate into an app and it actually provides solid UX, considering the amount of headache it solves. But in order to achieve all these traits, the II team had to sacrifice something - ease of Open Services integration.

I’m bringing this term “Open Service” here on purpose. I love this vision of global connected “internet of dapps” and I believe in it. And since Dfinity is the origin of this vision (at least of its modern reincarnation), I think, everything Dfinity does should help this vision to spread and mature.

Imagine the following use-case:
I want to implement a “feed aggregator” service, that will let its users to connect different social media dapps (imagine there are some) and see all their feed in one place. All dapps use II for authentication.

What do I need to solve?

  1. I need to learn how to use all the APIs provided by different social media dapps. But this is fine - this is actually the only “business” task I have. No problem.
  2. I need to somehow force my users to tell me their principals they use to interact with these social media dapps. This is a problem. There is no pattern for this right now. There is no II functionality for this either.

Bad solution 1

In order to solve this task I could provide a user with a form where they can enter their principal at the target dapp. But this will require the user to open a separate browser window with the dapp, login with II and then copy/paste the principal into the form.

Pros:

  • can be done right now;
  • secure - the user only tells me the principal so I’m only able to fetch public data.

Cons:

  • poor UX;
  • the user can manipulate input data.

Bad solution 2

I could also try to “be my own II” and force the user to authenticate themselves via their secure device, supplying target dapps hostname to the II’s backend. This would not only provide me with the right principal, but also with the authenticated session keypair, which my frontend could use to not only read, but to also write something on behalf of the user. This is good, right? Wrong!

Pros:

  • can be done right now;
  • okay UX;
  • the user can’t manipulate input data.

Cons:

  • this is not secure at all, don’t recommend.

Good solution

So, it seems like we’re out of luck trying to use current II’s implementation to solve this issue.
But it would be nice and very convenient, if the II could do it for us. Imagine this workflow:

  1. The user opens the aggregator’s webpage.
  2. The user clicks “Login with II” button.
  3. The II window opens.
  4. On this window the user sees the usual “touch your security device” dialog.
  5. Once this done, the II window doesn’t redirect the user back to the app, but instead shows them an additional window
This application asks you to reveal your identity on the following applications: 
https://social-media1.ic.app []
https://social-media2.ic.app []
...

Would you like to do that? Select apps you would like to reveal your identity of.
  1. The user checks some hostnames.
https://social-media1.ic.app [x]
https://social-media2.ic.app []
  1. The user clicks “Reveal” button.
  2. The user is redirected back to the aggregator app, but now they have not only the certified identity data (subnet signature and the delegation), but also the certified principal mapping data for selected services, which I, as a developer, can supply into my canisters and use on a frontend side:
data: https://social-media1.ic.app -> aaaa-aaa-aaaa-aaaa-aaaa
signature: <blob>
  1. Problem solved.

Pros:

  • good UX;
  • the user can’t manipulate input;
  • secure.

Cons:

  • can’t be done right now.

This solution is the desired one (at least from my current perspective), since it finally enables us to build complex interconnected services.
It is a bridge building factory in the world of islands.

This way we’re basically implementing “bad solution 1”, but inside the II. The app that asks for a reveal has access to principals only. The user has full control over this process. The list of hostnames to reveal could be provided by the requesting dapp developer (e.g. manually passed to AuthClient js object).

Questions

Is it possible to implement something like the “Good solution” I’ve described within the II?
How can I help to speed it up?

7 Likes

I like the intent behind this, but I think we also need to consider the possible attacks that can be performed down the line. The canisters “all look alike” for all intents and purposes, so what’s stopping anyone to ask for said tokens for dscvr / distrikt, etc on their own platform and use those delegations for nefarious purposes?

From a dev point of view, sure you asked and the user agreed. But from what’s been happening with cookies on-line, users complain about “I agree” fatigue, and often report clicking “next next netx” just so they can consume the content they want.

1 Like

It seems like trying to explain better I forgot to underline the important stuff.

No delegations should be issued during the described flow - only the information about principals of this user.

You log in - this issues the delegation.
You reveal principals - this issues only the certified mapping “url → principal”.
That’s the difference. The flow above just imagines both these actions performed during a single “Login with II” process.

This way the requesting app could only “read” (and only something that is intended for such reads) but not “write”.

1 Like

Then why would you need any kind of (read only) delegation / certified mapping? Isn’t that like scraping a public site? Your app should ask for the “nickname / username” that you’re using on the other platforms, and just query those platforms for the public feed based on that username.

Perhaps I’m not understanding a key concept of what you’re trying to achieve…

The way I see it, if something is public you don’t need a delagation / certified mapping. If something is not public, and you do need some kind of authentication, even if it’s “read only”, then it could potentially be abused if the only thing preventing said abuse is a “next next next” flow. Users will eventually get tired and just click whatever gets them their content sooner.

You’re right. If something is public, I don’t really need any certified data for the task. But if my task is “attach more data related to that identity” then I want 100% to be sure that this user is not manipulating the input.

It’s my fault. I have this idea in my head, but it’s hard to describe well for whatever reason.
Let me try another example.

Let’s imagine I want to build a service “Best doctor of the year award” which awards doctors who helped at least 1000 patients this year with a digital diploma and some tokens of mine. But only those doctors, who explicitly applied for the award on my webpage.

Somewhere else on the IC there are ledgers with the history of medical treatments (let’s imagine there many of such). Once a doctor applies for an award, I want to automatically check these ledgers and count how many patients did this doctor helped to. I don’t want this doctor to impersonate for someone else - otherwise the award would be compromised, so I can’t trust the user’s input on that.

It would be very nice if II could give me the certificate saying: “yes, this user was logged in to ‘https://treatment-ledger.ic.app’ with this principal ‘aaa-aaa-aaaa’ that I issued to him myself during the login procedure”.

Once I have this proof, I can be sure, that the applying user is a legit doctor, and then I could automate the whole process. And it would be convenient to generate this certificate during described above “reveal” phase of the “Login with II” process.

The flow I propose is optional. If your service doesn’t need any data from the outside - it’s fine not to ask it.
Also, since you’re principals does not change often, it’s fine to only ask this information once - first time the user tries to use a functionality that needs this information, and then just store it.
Or the II itself could remember what service did ask for a reveal and then reveal automatically (without asking) next time the user logs in on that service (until the user explicitly resets this option).

In other words, the UX could be improved and it’s fine.

So I think I understand what you want to build. It’s basically a reputation system that can’t be tampered with by the user. I still think the proposed flow is not the best for it tho. Let me try and explain again:

The main problem in my mind, with this flow, is that it can lead to “agree” fatigue. That’s to say that if a lot of apps want to use this flow, users will eventually stop paying attention and just expect it to “ask for rights”. This can be further primed with alerts, pictures, print-screens and such by a nefarious actor. What this could lead to is people visiting say a “music app” and being asked to share access to a trading app (again, remember that the canisters all “look” the same). Now the owner of the music canister can see your trading stats.

The way I’d design a system of reputation is with opt-in on each site, using a 3’rd party canister (call it an avatar canister) with a public facing interface, and privacy controls behind an authentication scheme. The flow would be from the social canisters to the “avatar”, every time an update is required. If you make that avatar canister as a (non-transferable) NFT then you also solve for discoverability.

So, each user has this NFT that’s called AVATAR. They link the avatar to dscvr, distrikt, etc. Each site uploads (with whatever granularity) status updates (e.g. 100 karma, -200 spam, etc). Other apps can either view the “public” info about the avatar (that the user can enable) or can request access to special, scoped rights. The user logs in to the Avatar app, approves and voila! you have your data.

This way you don’t need to modify the II and you also never compromise the anonymity of unwilling users that just want to click trough messages to get to the content.

Hm… I see. Thanks for your suggestion, but I think in practice it will end up providing worse UX than the II-based solution.

This avatar canister will require some kind of approval from the user if they’re willing to share their data with a service. If there are many services that would like to exchange data, it will require many user approvals.
As many as it was with the II example (so it won’t help with the “agree” fatigue). But additionally the user would need to log in to the avatar canister itself, adding +1 login procedure to the flow.

Also… I believe the solution you’re proposing won’t settle in, since it’s unnatural for services to store data about their users (we’re talking not only about karma or rating) outside their ecosystem perimeter. It would be hard to use such remote data to perform searches and other analytics. Storing the same data on both ‘avatar’ canister and ‘service’ canister could introduce data synchronization problems due to bugs or network failures.

So, I believe it is better to not exchange data, but exchange identities (principals) instead. I think the solution proposed by you can be used for that, but it will still require user to log in to avatar canister each time they need to approve an identity disclosure, so it would be better if this functionality would live inside the Internet Identity.

Also… I’m very into semantics of words and how it can help us better understand the problem.
Notice how the word “avatar” brings the same vibes as “identity”. This semantic similarity could be a clue of solution similarity.

I feel like we’re getting somewhere and fleshing out a potentially viable use-case :slight_smile:

On the logging in side, I basically see this “avatar” canister as an II alternative, down the road. Think of it as a stoic identity thing. Probably can even be a stoic wallet with things on top.

On the canisters sharing data, I think it can work like this: User wants “some” of his “achievements” published. They select what achievements want displayed in avatar from dscvr / district, and then the canisters only push updates (e.g. karma status / 24h, or karma level 1 (1-100) or karma silver elite 4’th rung master of lightning)… It doesn’t have to be all the data, just “achievements”. If the platforms make them into NFTs you can probably already roll that with the stoic identity thing. You’d just need a display app on top of it, and you have most of the functionality.

Regarding “agree” fatigue, I still see it differently. I think asking for access to multiple canisters at one time is wrong. I’d much rather have users be asked about ONE canister (e.g. the one they want to use, the one they clicked on) that wants access to their avatar profile. And then they can go and customize it if they want. But once they go and customize it, they see actual achievements and names. And can visually see something wrong if it is nefarious (wait, why is that music app requesting access to my achievement of trader of the month from awesometradingapp?)…

In other words, having the flow be social app → avatar, over time, can lead to a better experience, and having your own canister can also allow you to label things. It can be the difference that’s needed.

And also, this is not exactly so. Since II works with hostnames instead of canister ids, apps on this screen would be very distinguishable from each other. And I propose to keep it the same way.
The user could check all the hostnames they asked to reveal their identity of and only reveal those which they’re comfortable to reveal.

Moreover, the UI could somehow underline sensitive application’s hostnames (e.g. NNS frontend) to help a user with his decision. Like “hey, this application also wants to know your identity on NNS, it is better not to tell it anyone”.

This list of “sensitive” application could be managed programmatically through NNS proposals.

UPD: this would work as intended once IC DNS is up and running.

Best TL;DR I can make:

Down the line you will end up needing access rights, granular selection of data, labeling, etc. It’s much easier to do that in a stand-alone canister than to add ALL of that functionality to II. You can use that canister as an identity service, eventually. IMO II should stay as simple as possible, and as privacy preserving as possible.

No, I think we’re out of sync again.

I don’t need granular data selection, labeling and other stuff.
All I want is to teach canisters to exchange user’s principals, when the user is okay with that.

The whole new project around it is meaningless, because this project would have like just 2 functions:

#[update]
fn reveal_my_identity(of_service_host: String, to_service_host: String);

#[query]
fn get_revealed_identity(of_service_host: String, to_service_host: String) -> Principal;

This is why it would fit very nice inside the II - it’s small, it uses the same data set as II uses, it uses the same certified data tree as II uses.

Marking this post as a solution so it would be highlighted at the top. Changing this thread’s tag to Roadmap.

The Proposal

Improve Internet Identity to make it’s users able to disclose their principal from one service to another.

This would require to update the following codebase sections:

  1. II’s frontend
    • add an additional principal disclosure dialog;
    • modify the window.on('message') listener, to also process disclosure requests from the parent page
  2. II’s backend
    • add an additional disclose_identity method, which is called only by the user itself, which would generate and return a subnet signature over the following data set for each requested hostname:
    {
        hostname: "https://example.ic.app",
        disclosed_principal: "aaaa-aaa-aaaa",
    }
  1. AuthClient js library
    • add an additional function disclose_identity that would open the II webpage and pass a user-supplied disclosure request to it
    • add an additional functionality to the existing login function, so it would let a user to pass both flows: “login flow” and “disclose flow” during the same session
4 Likes

In the not too distant future, that application will probably need to access thousands of different social media apis. And the more sites that a person belongs to, the more of a chore it becomes to click on the button to link their II to the site.
As far as UX, I think the best solution would be one in which they click on the aggregator dapp once, and allows it to track the principal across the sites.

But, this is another idea with the
Cons :

  • can’t be done right now.

While this is a problem definitely, I believe this situation
principals.drawio (2)
is much more likely to happen, than what you’re saying
principals.drawio (3)
since it might be more useful for developers to request principals associated with more higher abstracted data.

I mean… the whole point of this feature (and Open Services vision) is to enable developers to build software on top of each other’s software. So, it is questionable if there would even exist this amount of similar social media apps - it is much better to make your app extendable from the outside.

But you’re right.

Have a look at this thread - https://www.reddit.com/r/dfinity/comments/qnf1nu/please_remove_proceed_step_after_ii_authentication/

The “proceed” step is seen as a nuisance, even if it’s currently there to actually protect users. No-one pays attention now, when the flow is extremely simple and straight forward. Complicating this flow will only lead to more confusion, and more “next next next” inattention.

I do agree with the “spirit” of the user’s request, though. Login should be as easy and straightforward as possible. Having to choose and select stuff on a login system would be bad UX. And it will lead to bad security down the line. KISS is the way, IMHO.

I am still having trouble understanding exactly why you’d want apps to know other principals, instead of having your users use a common identity (stoic / other).

Doesn’t even work: the security device only lets the frontend at identity.ic0.app use these keys; from your frontend you’ll see different keys. I guess you could ask the user for their recovery key…

Definitely it will work only if users will pass the recovery phrase, because webauthn is tied to the origin.

1 Like

I’d solve this independent of the Internet Identity:

  • The question “who are you on service X” is a valid question even if X uses it’s own authentication method, or an alternative identity provider (stoic identity? Other upcoming solutions?)
  • It’s good for security if the II stays simple and does one thing well.
  • Some applications may want to use an internal user id or use name or something else to identify users, and have an internal mapping from II pseudonym to user record.

Instead, I’d define a simple frontend side protocol, similar to the II auth protocol, based on opening a new tab and using postMessage. The user clicks “add DSCV“ on the feed collector website, a new tab on the DSCV domain opens, the user logs in (if the session isn’t live yet), approves the action (or it’s automatic, up to the other service), the application specific user identifier is sent back using postMessage, the tab closes. Done.

No changes to II needed, no expensive canister signatures, flexible enough to support other authentication schemes, other ways to identify users, and even works works with sites not (yet) on the IC.

No need for centralized components when a problem can be solved using decentralized protocols.

5 Likes

Another advantage is that DSCV doesn’t need to reveal the real principal of the user, it can use an internal one which can be ‘revoked’.

1 Like

So, you propose to develop such a pattern and somehow ask all the services to implement it on their side? Why would they do that? This, as I see that, would feel unnatural to implement, since after all the user is motivated to disclose it’s identity to another service, not the developer.

What should I do, if I’m working on like 10 interconnected microservices at the same time? Should I copy/paste this same code to all of my frontends/backends or what?

I believe, since the II originated this problem (no other present solution generates a separate user principal for each service) it would be fair if you guys provide a tool like that. If, in your opinion, there is no need for such a tool, then you could at least teach us how to set up II-powered services integration without it.

1 Like