-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #11560 - Implement EIP-4361 Sign-In With Ethereum #11883
Closed
Closed
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
436ca41
Issue #11560 - Implement EIP-4361 Sign-In With Ethereum
lachlan-roberts 90e5919
PR #11883 - javadoc and code cleanup
lachlan-roberts ac5925a
PR #11883 - test fixes and cleanup
lachlan-roberts 70e6192
PR #11883 - fixes to EthereumCredentials
lachlan-roberts 614025a
Add openid and siwe documentation sections.
lachlan-roberts 4006228
Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-…
lachlan-roberts 225e89c
update version for jetty-siwe pom.xml
lachlan-roberts aa945d5
add jetty module for siwe
lachlan-roberts 52c6c88
add siwe.mod, distribution tests and documentation
lachlan-roberts 9ea7431
changes from review
lachlan-roberts 32b7043
add javadoc for test classes
lachlan-roberts 2f66835
PR #11883 - changes from review
lachlan-roberts 44286fe
PR #11883 - changes from review
lachlan-roberts f1f1602
PR #11883 - changes from review
lachlan-roberts 9581ef1
Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-…
lachlan-roberts 37af005
Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-…
lachlan-roberts cc61f78
update poms to 12.0.13-SNAPSHOT
lachlan-roberts a525b70
Move SignInWithEthereumEmbeddedExample to code-examples in documentation
lachlan-roberts 063e3fc
PR #11883 - fix build issue with poms
lachlan-roberts 37aed0e
PR #11883 - remove experimental warning and fix to documentation
lachlan-roberts b07a8c0
Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-…
lachlan-roberts File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...es/src/main/java/org/eclipse/jetty/docs/programming/security/siwe/SignInWithEthereum.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. | ||
// | ||
// This program and the accompanying materials are made available under the | ||
// terms of the Eclipse Public License v. 2.0 which is available at | ||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 | ||
// which is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
// | ||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 | ||
// ======================================================================== | ||
// | ||
|
||
package org.eclipse.jetty.security.siwe.example; | ||
|
||
import java.io.PrintWriter; | ||
import java.nio.file.Paths; | ||
import java.util.Objects; | ||
|
||
import org.eclipse.jetty.http.HttpHeader; | ||
import org.eclipse.jetty.io.Content; | ||
import org.eclipse.jetty.security.AuthenticationState; | ||
import org.eclipse.jetty.security.Constraint; | ||
import org.eclipse.jetty.security.SecurityHandler; | ||
import org.eclipse.jetty.security.siwe.EthereumAuthenticator; | ||
import org.eclipse.jetty.server.Handler; | ||
import org.eclipse.jetty.server.Request; | ||
import org.eclipse.jetty.server.Response; | ||
import org.eclipse.jetty.server.Server; | ||
import org.eclipse.jetty.server.ServerConnector; | ||
import org.eclipse.jetty.server.handler.ContextHandler; | ||
import org.eclipse.jetty.server.handler.ResourceHandler; | ||
import org.eclipse.jetty.session.SessionHandler; | ||
import org.eclipse.jetty.util.Callback; | ||
|
||
public class SignInWithEthereum | ||
{ | ||
public static SecurityHandler createSecurityHandler(Handler handler) | ||
{ | ||
// tag::configureSecurityHandler[] | ||
// This uses jetty-core, but you can configure a ConstraintSecurityHandler for use with EE10. | ||
SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped(); | ||
securityHandler.setHandler(handler); | ||
securityHandler.put("/*", Constraint.ANY_USER); | ||
|
||
// Add the EthereumAuthenticator to the securityHandler. | ||
EthereumAuthenticator authenticator = new EthereumAuthenticator(); | ||
securityHandler.setAuthenticator(authenticator); | ||
|
||
// In embedded you can configure via EthereumAuthenticator APIs. | ||
authenticator.setLoginPath("/login.html"); | ||
|
||
// Or you can configure with parameters on the SecurityHandler. | ||
securityHandler.setParameter(EthereumAuthenticator.LOGIN_PATH_PARAM, "/login.html"); | ||
// end::configureSecurityHandler[] | ||
return securityHandler; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
documentation/jetty/modules/programming-guide/pages/security/index.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. | ||
// | ||
// This program and the accompanying materials are made available under the | ||
// terms of the Eclipse Public License v. 2.0 which is available at | ||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 | ||
// which is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
// | ||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 | ||
// ======================================================================== | ||
// | ||
|
||
= Jetty Security | ||
|
||
TODO: introduction | ||
180 changes: 180 additions & 0 deletions
180
documentation/jetty/modules/programming-guide/pages/security/siwe-support.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. | ||
// | ||
// This program and the accompanying materials are made available under the | ||
// terms of the Eclipse Public License v. 2.0 which is available at | ||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 | ||
// which is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
// | ||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 | ||
// ======================================================================== | ||
// | ||
|
||
[[siwe-support]] | ||
= SIWE Support | ||
|
||
== Introduction | ||
|
||
Sign-In with Ethereum (SIWE) is a decentralized authentication protocol that allows users to authenticate using their Ethereum account. | ||
|
||
This enables users to retain more control over their identity and provides an alternative to protocols such as OpenID Connect, which rely on a centralized identity provider. | ||
|
||
Sign-In with Ethereum works by using off-chain services to sign a standard message format defined by EIP-4361 (https://eips.ethereum.org/EIPS/eip-4361). The user signs the SIWE message to prove ownership of the Ethereum address. This is verified by the server by extracting the Ethereum address from the signature and comparing it to the address supplied in the SIWE message. | ||
|
||
Typically, you would rely on a browser extension such as MetaMask to provide a user-friendly way for users to sign the message with their Ethereum account. | ||
|
||
=== Support | ||
|
||
Currently Jetty only provides support SIWE in Jetty 12.0+ and only for `jetty-core`, and `ee10`+ environments. It is enabled by adding the `EtheremAuthenticator` to the `SecurityHandler` of your web application. | ||
|
||
== Usage | ||
|
||
=== Enabling SIWE | ||
The Sign-In with Ethereum module can be enabled when using Standalone Jetty with. | ||
[source,subs=attributes+] | ||
---- | ||
$ java -jar $JETTY_HOME/start.jar --add-modules=siwe | ||
---- | ||
|
||
If using embedded Jetty you must add the `EthereumAuthenticator` to your `SecurityHandler`. | ||
|
||
=== Configuration | ||
|
||
Configuration of the `EthereumAuthenticator` is done through init params on the `ServletContext` or `SecurityHandler`. The `loginPath` is the only mandatory configuration and the others have defaults that you may wish to configure. | ||
|
||
Login Path:: | ||
* Init param: `org.eclipse.jetty.security.siwe.login_path` | ||
* Description: Unauthenticated requests are redirected to a login page where they must sign a SIWE message and send it to the server. This path represents a page in the application that contains the SIWE login page. | ||
|
||
Nonce Path:: | ||
* Init param: `org.eclipse.jetty.security.siwe.nonce_path` | ||
* Description: Requests to this path will generate a random nonce string which is associated with the session. The nonce is used in the SIWE Message to avoid replay attacks. The path at which this nonce is served can be configured through the init parameter. The application does not need to implement their own nonce endpoint, they just configure this path and the Authenticator handles it. The default value for this is `/auth/nonce` if left un-configured. | ||
|
||
Authentication Path:: | ||
* Init param: `org.eclipse.jetty.security.siwe.authentication_path` | ||
* Description: The authentication path is where requests containing a signed SIWE message are sent in order to authenticate the user. The default value for this is `/auth/login`. | ||
|
||
Max Message Size:: | ||
* Init Param: `org.eclipse.jetty.security.siwe.max_message_size` | ||
* Description: This is the max size of the authentication message which can be read by the implementation. This limit defaults to `4 * 1024`. This is necessary because the complete request content is read into a string and then parsed. | ||
|
||
Logout Redirect Path:: | ||
* Init Param: `org.eclipse.jetty.security.siwe.logout_redirect_path` | ||
* Description: Where the request is redirected to after logout. If left un-configured no redirect will be done upon logout. | ||
|
||
Error Path:: | ||
* Init Param: `org.eclipse.jetty.security.siwe.error_path` | ||
* Description: Path where Authentication errors are sent, this may contain an optional query string. An error description is available on the error page through the request parameter `error_description_jetty`. If this configuration is not set Jetty will send a 403 Forbidden response upon authentication errors. | ||
|
||
Dispatch:: | ||
* Init Param: `org.eclipse.jetty.security.siwe.dispatch` | ||
* Description: If set to true a dispatch will be done instead of a redirect to the login page in the case of an unauthenticated request. This defaults to false. | ||
|
||
Authenticate New Users:: | ||
* Init Param: `org.eclipse.jetty.security.siwe.authenticate_new_users` | ||
* Description: This can be set to false if you have a nested `LoginService` and only want to authenticate users known by the `LoginService`. This defaults to `true` meaning that any user will be authenticated regardless if they are known by the nested `LoginService`. | ||
|
||
Domains:: | ||
* Init Param: org.eclipse.jetty.security.siwe.domains | ||
* Description: This list of allowed domains to be declared in the `domain` field of the SIWE Message. If left blank this will allow all domains. | ||
|
||
Chain IDs:: | ||
* Init Param: org.eclipse.jetty.security.siwe.chainIds | ||
* Description: This list of allowed Chain IDs to be declared in the `chain-id` field of the SIWE Message. If left blank this will allow all Chain IDs. | ||
|
||
=== Nested LoginService | ||
|
||
A nested `LoginService` may be used to assign roles to users of a known Ethereum Address. Or the nested `LoginService` may be combined with the setting `authenticateNewUsers == false` to only allow authentication of known users. | ||
|
||
For example a `HashLoginService` may be configured through the `jetty-ee10-web.xml` file: | ||
[, xml, indent=0] | ||
---- | ||
<Configure id="wac" class="org.eclipse.jetty.ee10.webapp.WebAppContext"> | ||
<Call id="ResourceFactory" class="org.eclipse.jetty.util.resource.ResourceFactory" name="of"> | ||
<Arg><Ref refid="Server"/></Arg> | ||
<Call id="realmResource" name="newResource"> | ||
<Arg><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Arg> | ||
</Call> | ||
</Call> | ||
|
||
<Call name="getSecurityHandler"> | ||
<Set name="loginService"> | ||
<New class="org.eclipse.jetty.security.HashLoginService"> | ||
<Set name="name">myRealm</Set> | ||
<Set name="config"><Ref refid="realmResource"/></Set> | ||
</New> | ||
</Set> | ||
</Call> | ||
</Configure> | ||
---- | ||
|
||
=== Application Implementation | ||
EIP-4361 specifies the format of a SIWE Message, the overview of the Sign-In with Ethereum process, and message validation. However, it does not specify certain things like how the SIWE Message and signature are sent to the server for validation, and it does not specify the process the client acquires the nonce from the server. For this reason the `EthereumAuthenticator` has been made extensible to allow different implementations. | ||
|
||
Currently Jetty supports authentication requests of type `application/x-www-form-urlencoded` or `multipart/form-data`, which contains the fields `message` and `signature`. Where `message` contains the full SIWE message, and `signature` is the ERC-1271 signature of the SIWE message. | ||
|
||
The nonce endpoint provided by the `EthereumAuthenticator` returns a response with `application/json` format, with a single key of `nonce`. | ||
|
||
=== Configuring Security Handler | ||
[,java,indent=0] | ||
---- | ||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/siwe/SignInWithEthereum.java[tags=configureSecurityHandler] | ||
---- | ||
|
||
=== Login Page Example | ||
|
||
Include the `Web3.js` library to interact with the users Ethereum wallet. | ||
[,html,indent=0] | ||
---- | ||
<script src="https://cdn.jsdelivr.net/npm/web3@1.6.1/dist/web3.min.js"></script> | ||
---- | ||
|
||
HTML form to submit the sign in request. | ||
[,html,indent=0] | ||
---- | ||
<button id="siwe">Sign-In with Ethereum</button> | ||
<form id="loginForm" action="/auth/login" method="POST" style="display: none;"> | ||
<input type="hidden" id="signatureField" name="signature"> | ||
<input type="hidden" id="messageField" name="message"> | ||
</form> | ||
<p class="alert" style="display: none;">Result: <span id="siweResult"></span></p> | ||
---- | ||
|
||
Add script to generate and sign the SIWE message when the sign-in button is pressed. | ||
[,html,indent=0] | ||
---- | ||
<script> | ||
let provider = window.ethereum; | ||
let accounts; | ||
|
||
if (!provider) { | ||
document.getElementById('siweResult').innerText = 'MetaMask is not installed. Please install MetaMask to use this feature.'; | ||
} else { | ||
document.getElementById('siwe').addEventListener('click', async () => { | ||
try { | ||
accounts = await provider.request({ method: 'eth_requestAccounts' }); | ||
const domain = window.location.host; | ||
const from = accounts[0]; | ||
|
||
// Fetch nonce from the server. | ||
const nonceResponse = await fetch('/auth/nonce'); | ||
const nonceData = await nonceResponse.json(); | ||
const nonce = nonceData.nonce; | ||
|
||
const siweMessage = `${domain} wants you to sign in with your Ethereum account:\n${from}\n\nI accept the MetaMask Terms of Service: https://community.metamask.io/tos\n\nURI: https://${domain}\nVersion: 1\nChain ID: 1\nNonce: ${nonce}\nIssued At: ${new Date().toISOString()}`; | ||
document.getElementById('signatureField').value = await provider.request({ | ||
method: 'personal_sign', | ||
params: [siweMessage, from] | ||
}); | ||
document.getElementById('messageField').value = siweMessage; | ||
document.getElementById('loginForm').submit(); | ||
} catch (error) { | ||
console.error('Error during login:', error); | ||
document.getElementById('siweResult').innerText = `Error: ${error.message}`; | ||
document.getElementById('siweResult').parentElement.style.display = 'block'; | ||
} | ||
}); | ||
} | ||
</script> | ||
---- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that TODO shown in the resulting documentation, or is it treated like a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shows the todo in the documentation, but the documentation is very incomplete and doesn't yet have any other security modules. And a bunch of the other main headers have the same TODOs for the introduction page.
But I will remove this index file and just have a header without an intro page for this.