This is a Proof-of-Concept React Native Expo mobile application that uses JSON Web Tokens to authenticate to an Internet Computer canister.
The main idea behind this PoC is to have an off-chain OpenID authentication service (Auth0 in this case) that mints a JWT that the user can send to the canister to generate a delegated identity. This way, the user can identify themselves on the canister with the same principal across sessions. It offers a trade-off between ease of use for the end user and decentralization of the authentication process.
This PoC has the following components:
- React Native Expo mobile app: src/app
- off-chain TypeScript backend: src/app_backend
- IC Rust backend canister: src/ic_backend
- Auth0 authentication provider
Head over to the How it works section for more details.
- 
Bun Javascript runtime 
- 
Rust with the wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown 
- 
dfx (better if installed with the dfx version manager - dfxvm)
- 
an Auth0 account 
- 
an Android/iOS device or simulator 
Follow these steps to configure Auth0:
- 
Create a Tenant and get your Auth0 Tenant domain, which looks like <TENANT_NAME>.<TENANT_REGION>.auth0.com
- 
In the Dashboard > Applications > YOUR_APP > Settings tab, set the Allowed Callback URLs and Allowed Logout URLs to: - io.icp0.jwtauthdemo.auth0://<YOUR_AUTH0_TENANT_DOMAIN>/ios/io.icp0.jwtauthdemo/callback
- io.icp0.jwtauthdemo.auth0://<YOUR_AUTH0_TENANT_DOMAIN>/android/io.icp0.jwtauthdemo/callback
 Where <YOUR_AUTH0_TENANT_DOMAIN>is the Auth0 Tenant domain andio.icp0.jwtauthdemois both the Android Package Name and iOS Bundle Identifier, as configured in the app.config.js file.
- 
In the Dashboard > Applications > YOUR_APP > Credentials tab, set the Authentication Method to None (instead of Client Secret (Post)) 
The 1st step of the Auth0 React Native Quickstart interactive guide can be helpful too.
Install the dependencies:
bun installCopy the .env.example file to .env:
cp .env.example .envand replace the values with your own.
Start the IC backend:
# in terminal 1
bun start:dfx
# in terminal 2
bun deploy:ic_backendStart the off-chain backend:
# in terminal 3
bun start:app_backendStart the mobile app:
# in terminal 4
cd src/app
cp ../../.env .env
bun expo prebuild
cd ../..
# if you want to start the app for Android
bun start:android
# if you want to start the app for iOS
bun start:iosYou may need to manually start the Android/iOS emulator.
See the expo start CLI docs for more information.
Unit tests are available for the IC Rust backend canister. Simply run:
./scripts/unit-test.shIntegration tests are available for the IC Rust backend canister. Simply run:
./scripts/integration-test.shThis PoC is highly inspired by this discussion on the Internet Computer forum.
sequenceDiagram
    participant M as Mobile app
    participant A as Authentication Provider
    participant C as Canister
    participant B as Off-chain Backend
    note over A,B: JWK data is shared
    note over M: Generates session PK/SK
    M->>+A: Request id_token with PK
    A->>B: Request new/existing user
    B->>A: User confirmed
    A->>-M: Valid id_token(PK)
    M->>+C: Request prepare_delegation with id_token(PK) signed with SK
    note over C: Validate id_token against JWK
    C-->>C: Extract sub claim from id_token
    C-->>C: Create a canister signature and store in delegation map
    C-->>C: Derive principal from canister signature
    C-->>C: Assign principal to sub in users map
    C->>-M: user_key, expiration
    M->>+C: Request get_delegation with id_token(PK), expiration signed with SK
    note over C: Validate id_token against JWK
    C-->>C: Read delegation from delegation map
    C->>-M: Signed Delegation
    note over M: Session PK/SK is now delegated
    note over M: User is authenticated
    M-->>C: Request authenticated with delegation signed with SK
    note over C: User identified by delegation principal
    C-->>C: Read sub from users map
    C-->>M: Confirm authentication
    M-->>B: Generic request with ic_token
    note over B: User identified by sub claim
    B-->>M: ...
    The main steps are:
- 
The JWKs must be fetched from Auth0 and stored in the canister and off-chain backend. In the current implementation, the canister fetches them once on deployment and every 1 hour using the HTTPS outcalls and Timers features. 
- 
The mobile app generates a new session PK/SK pair; 
- 
The mobile app requests an id_tokenfrom the authentication provider, setting thenonceclaim to the session PK (encoded as a hex string);
- 
The authentication provider creates the new user or fetches the existing user on the off-chain backend/database, then mints a valid id_tokenthat contains thenonceclaim as requested;
- 
The mobile app sends an update call to the prepare_delegationmethod of the canister, with theid_tokenas argument. This update call is signed with the session PK/SK pair;
- 
The canister performs the following operations: a. Validates the id_tokenagainst the JWK and by verifies that:- it was issued by the JWKs fetched from Auth0
- it is not expired (expclaim)
- it was not issued more than 10 minutes ago (iatclaim)
- the issuer is the expected Auth0 tenant (issclaim)
- the audience is the expected Auth0 application id (audclaim)
- the session self-authenticating principal derived from the session PK is equal to the caller (nonceclaim)
 b. Extracts the subandnonceclaims from theid_tokenc. Hashes the subandnonceclaims together with a randomsalt. The DER-encoding of this hash is theuser_keyd. Creates a canister signature for the user_keyand stores it in thedelegationmape. Derives a self-authenticating principal from the user_key. This is the principal with which the mobile app will authenticate to the canister and will be the same across sessions and canister upgrades.f. Assigns the principal to the subclaim in theusersmap. This map is used to retrieve the usersubclaim in all the methods that the user will use after completing the authentication flow, see step 7.If all these steps succeed, the canister returns the user_keyand the expiration of the delegation, which is set to theexpclaim of theid_token.
- 
The mobile app sends a query call to the get_delegationmethod of the canister, with theid_tokenandexpirationas arguments. This query call is signed with the session PK/SK pair.This method performs the same validation on the id_tokenas in the previous step and returns the delegation along with the canister signature.
- 
The mobile app can now create a delegated identity, with which it can send subsequent requests to the canister, for example to the authenticatedmethod.The authenticatedmethod is just a demo method to show that the user is authenticated with the delegation obtained from the canister and itssubclaim can be retrieved from theusersmap.
- 
The mobile app can also send authenticated requests to the off-chain backend, which can identify the user by the subclaim as well.
The canister_sig_util crate from the Internet Identity source code is used as an helper for the signatures map.
- 
On the canister, periodically fetch the JSON Web Key Sets (JWKS) from Auth0 using the HTTPS outcalls and Timers features. Right now, the JWKS are fetched at build time by the build-canister.sh script, stored in data/jwks.jsonand imported in the canister as raw bytes at compile time (source).Fetching the JWKS at runtime is needed because JWKs on Auth0 may rotate. Related issue: #1. 
- 
Integration tests Related PRs: 
MIT License. See LICENSE.