Using the pre built ic_siwe_provider
canister is the easiest way to integrate Ethereum wallet authentication into an Internet Computer (ICP) application.
The canister is designed as a plug-and-play solution for developers, enabling easy integration into existing ICP applications with minimal coding requirements. By adding the pre built ic_siwe_provider
canister to the dfx.json
of an ICP project, developers can quickly enable Ethereum wallet-based authentication for their applications. The canister simplifies the authentication flow by managing the creation and verification of SIWE messages and handling user session management.
ic_siwe_provider
is part of the ic-siwe project that enables Ethereum wallet-based authentication for applications on the Internet Computer (ICP) platform. The goal of the project is to enhance the interoperability between Ethereum and the Internet Computer platform, enabling developers to build applications that leverage the strengths of both platforms.
- Prebuilt: The canister is pre built and ready to use.
- Configurable: The
ic_siwe_provider
canister allows developers to customize the SIWE authentication flow to suit their needs. - Easy Integration: The canister can be easily integrated into any Internet Computer application, independent of the application's programming language.
- Keeps Ethereum Wallets Private: The canister never has access to the user's Ethereum wallet, ensuring that the user's private keys are never exposed.
- Session Identity Uniqueness: Ensures that session identities are specific to each application's context, preventing cross-app identity misuse.
- Consistent Principal Generation: Guarantees that logging in with an Ethereum wallet consistently produces the same Principal, irrespective of the client used.
- Direct Ethereum Address to Principal Mapping: Creates a one-to-one correlation between Ethereum addresses and Principals within the scope of the current application.
- Timebound Sessions: Allows developers to set expiration times for sessions, enhancing security and control.
- Integration overview
- Installation
- Runtime Features
- Service Interface
- Data Structures
- Updates
- Contributing
- License
See the ic-siwe-react-demo-rust for a complete example of how to integrate the ic_siwe_provider
canister into an ICP application. The easiest way to get started is to fork the demo and modify it to suit your needs.
The integration tests for the ic_siwe_provider
canister also provide a good overview of how to integrate the canister into an ICP application.
The high-level integration flow for the ic_siwe_provider
canister is as follows:
- An ICP application requests a SIWE message from the
ic_siwe_provider
canister on behalf of the user. - The application displays the SIWE message to the user who signs it with their Ethereum wallet.
- The application sends the signed SIWE message to the
ic_siwe_provider
canister to login the user. The canister verifies the signature and creates an identity for the user. - The application retrieves the identity from the
ic_siwe_provider
canister. - The application can now use the identity to make authenticated calls to canisters.
The canister is pre built and ready to use. To add it to your project, simply add it to the dfx.json
file in the canisters
section:
{
"canisters": {
"ic_siwe_provider": {
"type": "custom",
"candid": "https://github.com/kristoferlund/ic-siwe/releases/download/v0.1.1/ic_siwe_provider.did",
"wasm": "https://github.com/kristoferlund/ic-siwe/releases/download/v0.1.1/ic_siwe_provider.wasm.gz"
}
}
}
The ic_siwe_provider
canister needs to be configured on deploy. The configuration is passed as an argument to the canister init
function. Below is an example of how to configure the canister using the dfx
command line tool:
dfx deploy ic_siwe_provider --argument $'(
record {
domain = "127.0.0.1";
uri = "http://127.0.0.1:5173";
salt = "mysecretsalt123";
chain_id = opt 1;
scheme = opt "http";
statement = opt "Login to the app";
sign_in_expires_in = opt 300000000000;
session_expires_in = opt 604800000000000;
targets = opt vec {
"'$(dfx canister id ic_siwe_provider)'";
"'$(dfx canister id my_app_canister)'";
};
}
)'
Important
domain
should be set to the full domain, including subdomains, from where the frontend that uses SIWS is served.
Example: myapp.example.com
uri
should be set to the full URI, potentially including the port number, of the frontend that uses SIWS.
Example: https://myapp.example.com:8080
See also additional runtime features that can be configured.
Below example uses the ic-siwe-js React hook to integrate the ic_siwe_provider
into a React application.
Wrap your application's root component with SiweIdentityProvider
to provide all child components access to the SIWE identity context.
// App.tsx
import { SiweIdentityProvider } from 'ic-siwe-js/react';
import { canisterId } from "../../ic_siwe_provider/declarations/index";
function App() {
return (
<SiweIdentityProvider
canisterId={/* Canister ID */}
// ...other props
>
{/* Your app components */}
</App>
);
}
Use the useSiwe
hook to initiate the login process:
// Component.tsx
import { useSiwe } from "ic-siwe-js/react";
function MyComponent() {
const { login, clear, identity, ... } = useSiweIdentity();
// ...
}
The runtime behaviour of the ic_siwe_provider
canister and the ic_siwe
library can be customized using the following settings:
Default: URI is not included in the seed
When set, the URI is included in the seed used to generate the principal. Including the URI in the seed does not add any additional security in a scenario where ic_siwe_provider
is deployed and configured to serve only one domain. However, if the ic_siwe
library is used in a custom canister, that delagetes identities for more than one domain, it is recommended to enable this feature to ensure that the principal is unique for each domain.
runtime_features = opt vec { \
variant { IncludeUriInSeed } \
};
Default: Mapping is enabled
When set, the mapping of Ethereum addresses to Principals is disabled. This also disables the canister endpoints get_principal
.
runtime_features = opt vec { \
variant { DisableEthToPrincipalMapping } \
};
Default: Mapping is enabled
When set, the mapping of Principals to Ethereum addresses is disabled. This also disables the canister endpoints get_address
and get_caller_address
.
runtime_features = opt vec { \
variant { DisablePrincipalToEthMapping } \
};
In addition to the SIWE endpoints, required by the useSiweIdentity
hook, this canister also exposes endpoints to retrieve the Ethereum address associated with a given ICP principal and vice versa. These endpoints are useful for applications that need to map ICP Principals to Ethereum addresses.
- Purpose: Retrieves the Ethereum address associated with a given ICP principal.
- Input: A
ByteBuf
containing the principal's bytes (expected to be 29 bytes). - Output:
Ok(String)
: The EIP-55-compliant Ethereum address, if found.Err(String)
: An error message if the principal cannot be converted or no address is found.
- Purpose: Retrieves the Ethereum address associated with the caller. This is a convenience function that internally calls
get_address
. - Output: Same as
get_address
.
- Purpose: Retrieves the principal associated with the given Ethereum address.
- Input: The EIP-55-compliant Ethereum address (
String
). - Output:
Ok(ByteBuf)
: The principal if found.Err(String)
: An error message if the address cannot be converted or no principal is found.
- Purpose: Generates a SIWE message challenge and returns it to the caller, initiating the login process.
- Input: Ethereum address (
String
). - Output:
Ok(PrepareLoginOkResponse)
: The prepared SIWE message and nonce.Err(String)
: An error message if there is an error in preparing the login.
- Purpose: Verifies the signature of the SIWE message and prepares the delegation for authentication.
- Input: Signature (
String
), Ethereum address (String
), session key (ByteBuf
), and nonce (String
). - Output:
Ok(LoginDetails)
: The public key and other login response data if the login is successful.Err(String)
: An error message if the login process fails.
- Purpose: Fetches the delegation to be used for authentication once the user is logged in.
- Input: Ethereum address (
String
), session key (ByteBuf
), and expiration timestamp (u64
). - Output:
Ok(SignedDelegation)
: The delegation if the process is successful.Err(String)
: An error message if there is a failure in fetching the delegation.
In addition to the key functionalities for Ethereum wallet authentication, the ic_siwe_provider
canister includes initialization and upgrade endpoints essential for setting up and maintaining the canister.
- Purpose: Initializes the
ic_siwe_provider
canister with necessary settings for the SIWE process. - Input:
SettingsInput
struct containing configuration details like domain, URI, salt, chain ID, etc. - Operation: Sets up the SIWE library with the provided settings. This function is invoked when the canister is created.
- Purpose: Maintains the state and settings of the
ic_siwe_provider
canister during an upgrade. - Input:
SettingsInput
struct similar to theinit
function. - Operation: Ensures that the SIWE settings and state are preserved and reapplied after the canister is upgraded.
pub enum RuntimeFeature {
// Enabling this feature will include the app frontend URI as part of the identity seed.
IncludeUriInSeed,
// Disable the mapping of Ethereum address to principal. This also disables canister endpoints `get_principal`.
DisableEthToPrincipalMapping,
// Disable the mapping of principal to Ethereum address. This also disables canister endpoints `get_address` and `get_caller_address`.
DisablePrincipalToEthMapping,
}
Defines the settings that controls the behavior of the ic_siwe_provider
canister.
pub struct SettingsInput {
// The full domain, including subdomains, from where the frontend that uses SIWE is served.
// Example: "example.com" or "sub.example.com".
pub domain: String,
// The full URI, potentially including port number of the frontend that uses SIWE.
// Example: "https://example.com" or "https://sub.example.com:8080".
pub uri: String,
// The salt is used when generating the seed that uniquely identifies each user principal. The salt can only contain
/// printable ASCII characters.
pub salt: String,
// Optional. The Ethereum chain ID for ic_siwe, defaults to 1 (Ethereum mainnet).
pub chain_id: Option<u32>,
// Optional. The scheme used to serve the frontend that uses SIWE. Defaults to "https".
pub scheme: Option<String>,
// Optional. The statement is a message or declaration, often presented to the user by the Ethereum wallet
pub statement: Option<String>,
// Optional. The TTL for a sign-in message in nanoseconds. After this time, the sign-in message will be pruned.
pub sign_in_expires_in: Option<u64>,
// Optional. The TTL for a session in nanoseconds.
pub session_expires_in: Option<u64>,
// Optional. The list of canisters for which the identity delegation is allowed. Defaults to None, which means
// that the delegation is allowed for all canisters.
pub targets: Option<Vec<String>>,
// Optional. The runtime features that control the behavior of the SIWE library.
pub runtime_features: Option<Vec<RuntimeFeature>>,
}
Contributions are welcome. Please submit your pull requests or open issues to propose changes or report bugs.
- kristofer@fmckl.se
- Twitter: @kristoferlund
- Discord: kristoferkristofer
- Telegram: @kristoferkristofer
This project is licensed under the MIT License. See the LICENSE file for more details.