Alternate User Authentication method in Canister

Anyone working on alternate (other than IC’s Internet Identity) methods of user authentication to be deployed in canister, such as email-based or sms-based… any experiences to share?

1 Like

Ya is anyone working on a way to access the portal and NNS on a browser without biometric or hardware key?

As far as I know, recently there was an update. Now you can manage your seed phrase by yourself (like on ethereum), without having to rely on any hardware solution.

1 Like

It’s also possible to use Ed25519 keypair to gen a self-authenticating principal in browser. The trick involves this lib:

import { Ed25519KeyIdentity } from '@dfinity/identity'

This is also the principal scheme used by dfx tool.

4 Likes

yes. working on it. With Metamask/wallectconnect as well

2 Likes

nice… are you also looking to have an Auth Oracle that would solve for Auth by connecting to sign-in options such as Google, Facebook, etc that have traditionally been used by developers?

1 Like

I just tried using the recovery phrase to login via my laptop and it didn’t work =[
I tried on 3 different browsers =[

But hopefully one of the options below will work!

Oh wow, thank you so much!

I’m fairly new to coding–I’m only fluent at using R for statistics–so I’ll have to look into figuring out this option.

Found this site, which might be of help!

Thank you again!

@neeboo Oh that would be brilliant! I’ll keep an eye out for that!!

@ICVF that would be helpful too actually! I just dont have a smart key so anything that works would be great

OK I’ve been thinking about this today. For reference, I’m building a native mobile app (in React Native) that talks to an IC canister backend.

Can someone tell me whether this would work, i.e. is secure? It’s a traditional email-password login flow instead of the Internet Identity flow.

  1. End user enters email and password in mobile app, and submits.
  2. Mobile app tries logging in using Firebase Auth (via their React Native SDK). If credentials are accepted, Firebase Auth returns a JWT ID token.
  3. Mobile app calls a login method on a custom IC canister, passing both the JWT and the Firebase Auth public key as arguments [1]. The identity (containing the principal) used to authenticate the request will be generated by the mobile app using Ed25519KeyIdentity.generate() and saved in local (mobile) storage.
  4. The canister validates the signature on the JWT using the Firebase public key [2], and then retrieves the uid (i.e. unique ID for the user). It saves the mapping between the principal in the request and the uid in some Map variable.
  5. Whenever the mobile app wants to do something on behalf of the logged in user, it no longer needs to pass the JWT and simply makes the request to the canister with the same identity/principal it generated in step 3. Basically, it’s a session ID.
  6. When the end user logs out, the mobile app calls signOut using the Firebase SDK. It then calls some logout method on the canister, which deletes the mapping from principal to uid. Lastly, it removes the identity from local storage.

The alternative is to completely do away with all IC identities/principals, use AnonymousIdentity for all canister-bound requests, and pass the JWT ID token to authenticate every should-be-authenticated request. The benefit is that it’s stateless, since the canister no longer needs to store any mapping and therefore we can get rid of the login and logout canister methods. Also, the Firebase SDK will automatically handle persisting tokens to local storage.

The drawback is that if someone happens to steal the ID token (even over HTTPS or some other way), then they have complete access to that end user’s info (until the token expires), whereas stealing a principal doesn’t matter if you can’t steal the associated private key. All they could do is replay the exact same request. Also, there’s probably a slightly performance drawback with validating JWT on every request—but on the other hand, you don’t need to validate the sender_sig on every request since we are using the anonymous principal.

I’m actually leaning towards the alternative option… Please tell me if I’m thinking about this correctly or am missing an obvious security risk.

[1] The public key is deliberately passed to the canister, because otherwise it would need to rely on an oracle to fetch the public key from an external Firebase URL. Instead, the mobile app client can fetch it.
[2] There’s an open-source Rust SDK for JWTs, but none for Motoko.

1 Like

There’s also a team that received a grant from dfinity for building an alternative to the II service. Reading through their presentation I got the feeling that they plan to allow users to create an identity on their service with “traditional” flows (e.g. e-mail).

1 Like

Yeah mate that’s how I would do it for if I were to use an external service as an authenticator, i.e. trusted service.

Pros and cons for both - I’d personally go with the session key idea, generating a ed key locally and submitting it once with the jwt. Would make it easier to just deal with Principals, and could also work cross canisters. E.g. you could use the login canister as a way to authenticate on the IC. When you submit the jwt, it updates the Principal attached to a user account with a given user ID.

You can then query external canisters that support this authentication method with your user ID. The canister can make a call to the login canister to ensure that the principal you are using actually is authorized to act as the user.

The jwt could technically be intercepted and used by someone else during the initial setup, but it wouldn’t be hard to get around this

The idea with the login <> external canisters is actually really interesting. Didn’t think of that, since I was thinking of having a single frontend canister accept all client requests, but maybe that doesn’t scale?

Can you clarify how you would get around the JWT being intercepted? I was planning on just relying on HTTPS for that.

One issue with this whole principal to user id thing I’m concerned about is whether I need a TTL for this mapping. For example, JWT ID (and access) tokens have expiry times. Even the DelegatedIdentity that the Internet Identity passes back to the client to use to authenticate with the IC has an expiration. A normal non-delegated Ed25519KeyIdentity doesn’t though… and I’ll be persisting the identity in local storage so maybe that’s a risk where it can get stolen?

Do you remember what team this was, or do you have a link to their presentation? Would definitely be interested in talking.

To make the JWT process more secure, you could do a 2 step approach:

  1. User generates a hash of the JWT and must submit it first along with the Principal to be linked
  2. User waits until this has been committed to the chain
  3. User then submits actual JWT, which is then linked to the account
  4. The canister only allows the JWT hash to be linked with a single account, so if someone were to intercept the JWT at stage 3, and try to submit the hash alongside a different Principal it wouldn’t work as the hash is already linked

Yeah expiry times it something that should be added to for sure.

1 Like

Maybe you are referring this?
AstroX: building Web3 Identity Service for 8 billion users | by AstroX | Jun, 2021 | Medium

1 Like

Fascinating! A crash course in security. Thanks, this makes sense.

1 Like

Yeah, astrox was the team that I read about.