Skip to content
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

Add a way to use webauthn without Javascript #1255

Open
ignaloidas opened this issue Jul 5, 2019 · 69 comments
Open

Add a way to use webauthn without Javascript #1255

ignaloidas opened this issue Jul 5, 2019 · 69 comments

Comments

@ignaloidas
Copy link

Adding a way to use webauthn without Javascript should be considered. For example most Tor users have Javascript always disabled, and having a way to use webauthn without JS would allow use of 2FA in Tor. Also, some projects want to be fully usable without the use of JS, and having a way to use 2FA would open the way for those projects too. I'm not too sure how it should be implemented though. Maybe <input type="webauthn">?

@lgarron
Copy link
Contributor

lgarron commented Jul 5, 2019

At GitHub, we try to minimize the use of Javascript on the frontend. We have plain forms at the core of most forms of authentication (e.g. username, password, SMS, OTP). There are some good reasons to use JS for webauthn, but in principle it would be nice if it was possible to shed the overhead in favor of simplicity for the common case.

@equalsJeffH
Copy link
Contributor

equalsJeffH commented Jul 10, 2019

yeah, nominally ISTM this'd require defining some form of declaration for the webapp to make that it wishes to employ webauthn and possibly defining a common format for conveying the webauthn bits between the client platform and the RP front-end server.

from discussion on webauthn call today: so which RPs would use this "declaritive approach" rather than the webauthn API ? how many RPs would rather use the approach proffered here rather than the webauthn API ?

@nadalin nadalin added this to the L3-WD-01 milestone Jul 10, 2019
@ddevault
Copy link

Hiya, I'm the one who asked @ignaloidas to suggest this on behalf of https://sourcehut.org. The inability to utilize this without JavaScript is a blocker for us, we will not add webauthn without it.

@gartov
Copy link

gartov commented Jul 12, 2019

I've spent some time thinking about creating a declarative HTML API that eliminates javascript. In this case, a lot of configuration properties need to be defined by the RP, somewhere. (i.e. the PublicKeyCredentialCreationOptions, etc. ) I think this should be done with a combination of both a config document hosted at the RP (written in JSON, retrieved by HTTPS), and properties defined locally in the HTML element. The local property values override the values in the config document. The config document and/or the (rel="capis-api") attribute can also serve as the "declaration" that the RP supports the API. I use a single HTML attribute to hold a JSON-style set of properties. @equalsJeffH is right. The browser must support one or more transport formats. For the browser to send the credentials to the RP. I suggest using a JSON Web Signature (JWS) as the default. (The object needs to contain the "action", "challenge", user_id, etc.) Just as important, is that the RP must issue a response, which is not a web page. It must be in a standard format, because the browser must parse it to see if the action succeeded. (It should probably be a JSON document.) Note that the API directly tells the browser the login status at the RP. (The browser may keep a list of all logged in RPs, and provide a "logoutAll" button.)

<a href="/old_style_login_form.html" rel="capis-api" capis-cfg=' "action":"ua/login", "redirect_uri":"/after_login.html", "endpoint_uri":"/validate_JWS.json", "capis_doc_uri":"/capis_config.json", "challenge":"H3uz78_k...", "transport_format":"JWS" '>Login</a>

The href is to catch OLD browsers that do not support the API. The new API does not use the href. (It uses the endpoint_uri.) "action" holds the requested operation. (Supports "login", "logout", "prove-user-presence", etc.) "capis_doc_uri" is the URL of the config document. "challenge" is a base64url encoded binary value. endpoint_uri is the URL the browser needs to send the credentials (JWS) to. redirect_uri is the page the browser should redirect to after user authentication success. Note that "endpoint_uri" and "transport_format" are not necessary in the HTML, they can be defined in the config document.

@emlun
Copy link
Member

emlun commented Jul 12, 2019

I had a vastly different idea for how this could work:

<form method="POST" action="/exampleapp/webauthn/finish_assertion">
    <input type="hidden" name="exampleapp_request_id" value="3a74f2b4-cff0-4f50-8076-2f5532a0d6f3"/>
    <input type="webauthn.get" name="public_key_credential" value='{
        "publicKey": {
            "challenge": "WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs",
            "allowCredentials": [{ "type": "public-key", "id": "Pzy_hxuR849qZbLAE8Vr4U3KPiIN6W10ssJOR55BMZQ" }],
        }
    }'/>
    <input type="submit"/>
</form>

That is, a perfectly normal form with nothing special except the new <input type="webauthn.get"> suggested in OP. The exampleapp_request_id field is an example application-specific reference to server-side state containing things like the username, a copy of the challenge, and probably where to redirect the user next. Of course the application could add any other fields to the form that they need, as usual.

The <input type="webauthn.get"> would probably render a button which starts the WebAuthn ceremony, and replace the button with a success/failure indicator after the ceremony finishes. When the form is submitted, this would send a POST as usual with the contents

POST /exampleapp/webauthn/finish_assertion
exampleapp_request_id=3a74f2b4-cff0-4f50-8076-2f5532a0d6f3&public_key_credential={"id":"K3xM080fiCDCkv412SdQ6--982rRf9i6NtDY0Jkv-AJZaCH9-MNs-ijV2y2OXKbxjzD3rXR05rmGF_jGQnXyPQ","response":{"authenticatorData":"xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cBAAAANQ==","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJwakpQTWFvUUFFSEhKb0NtR0lVd3EtclZBc0JzRDY3Q2pCVDhMYXJwcEtjIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9kZW1vLnl1Ymljby5jb20iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=","signature":"MEUCIHwdgFLpCi05C1SeqRIBEwM1iJr4HJu8H9YC49vlTd+OAiEAxGDHU0+zNrgk8eSrx9KD8bj6jqRiKdPNz2ANA7EShqI="},"clientExtensionResults":{},"transports":["usb"]}

or in the case of failure something like:

POST /exampleapp/webauthn/finish_assertion
exampleorg_request_id=3a74f2b4-cff0-4f50-8076-2f5532a0d6f3&public_key_credential={"error":"NotAllowedError","message":"The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission."}

This way we would reuse the existing JSON/JS data structures, and the only new thing to add would be how <input type="webauthn.create"> and <input type="webauthn.get"> work (and how to en/decode the binary values to/from JSON). All other details about data transport, URLs, redirects etc. would be left to the RP to implement however they please with already existing tools.

@gartov
Copy link

gartov commented Jul 12, 2019

@emlun Nice. Our ideas have a lot of similarities. They both embed JSON in an HTML attribute, include a challenge, and send a POST to the RP. They both add one or two new types of HTML element. (Mine adds two new HTML attributes, yours adds an input type="webauthn.get" and "webauthn.create".) Both of our ideas add something to the anchor or form to explicitly identify it as performing user authentication. So the browser does not have to guess which login forms or anchors are used for user authentication. My idea adds a special attribute to the anchor element. With your idea, the browser can test for the existence of either special input element. (type="webauthn.get") Both of our ideas require a new ability from browsers, to intercept the click or submit and perform the WebAuthn ceremony. Then put the results of the ceremony into a POST.

My idea can be changed to behave like yours. My original idea was to send a JSON Web Signature, but that can be changed to send the existing JSON/JS data structure. Similarly, my idea can be modified to include a reference to a server side state, and the "publicKey" and "allowCredentials" properties. If possible, the entire JSON configuration document hosted at the RP (that I suggest) may be omitted. (Although there may be extra reasons to keep it. Allows the full options, it is extensible, and see below.) The end result is that the two ideas can send the exact same POST to the RP. As in your example.

The real differences between our ideas seems to be:
(1) I use an anchor. (Although a form can be used too. If necessary.) I am intentionally trying to remove login forms, and make them unnecessary. The browser detects the special attribute, renders the anchor specially. On click, it starts the WebAuthn ceremony, and will send a POST to the endpoint_uri.

(2) My idea has an explicit response to the login request from the RP. This informs the browser of the success/fail and state of the login. I want the browser to be able to keep track of which RP are logged in, so it can provide a "logoutAll" option. It does this by having the RP send a JSON response to the browser, with success or error information. If it can be worked out that the RP can include redirect information in the response, so much the better. The browser can read the "redirect_uri" property etc. from the response, and redirect to the desired page. Or if the redirect_uri is missing, the browser will refresh the existing page by default.

(2b) The response from the RP is caught by the browser, parsed, and the browser updates the Authenticator. With either data from the response, or data stored from the request. (The browser calls navigator.credentials.store)

(3) My idea includes an explicit fallback mechanism. In case the browser does not support the new API. (If the browser does not understand the input type="webauthn.get" HTML element.)

(4) My idea has a discrete "action" property. This allows both "login" and an explicit "logout" request. It also allows the API to be easily extended in the future with other actions. "prove-user-presence", "change-credentials", etc. (i.e. I want to keep the browser informed of what user authentication action is being performed, and what the response is. The browser needs to update the Authenticator with the result, and needs to maintain the login status of the RP.)

(5) My idea does not have an "action":"create-user-login" option in the HTML. I figure that "action":"login" is enough. It can be used for both "get" and "create". The browser or the user can figure out that the Authenticator has no stored credentials for the RP, and create a POST with the "webauthn.create" option.

(6) It may be beneficial to allow the JSON configuration document hosted by the RP as an option. (accessible via HTTPS or FTPS, etc.) My idea is that it may be possible to use this document to perform user authentication for other applications beyond browsers and HTML. Imagine using the same Authenticator to login to other applications, online games (i.e. Steam, World of Warcraft), FTP, etc. As long as those other applications can parse JSON, make a call to the Authenticator, and create the desired transport data. (i.e. the existing JSON/JS data structure or a JSON Web Signature.) I imagine that eventually the Operating System will provide an API to connect to the Authenticator.

@ignaloidas
Copy link
Author

@Garnac I have a few problems with your idea:

(1) I use an anchor. (Although a form can be used too. If necessary.) I am intentionally trying to remove login forms, and make them unnecessary.

It should be a form IMO. You are sending info from browser to the server, the usual way to do this without Javascript is

(2) My idea has an explicit response to the login request from the RP. This informs the browser of the success/fail and state of the login. I want the browser to be able to keep track of which RP are logged in, so it can provide a "logoutAll" option.

This doesn't make sense with webauthn.

(2b) The response from the RP is caught by the browser, parsed, and the browser updates the Authenticator. With either data from the response, or data stored from the request. (The browser calls navigator.credentials.store)

Again, this doesn't make sense with webauthn, navigator.credentials.store is invalid with webauthn

(3) My idea includes an explicit fallback mechanism. In case the browser does not support the new API. (If the browser does not understand the input type="webauthn.get" HTML element.)

Adding a hidden to the will make it unnoticable for browsers that don't support it, and then Javascript webauthn API can be likely used as a fallback mechanism. In my mind this is more of an alternative for users who would block Javascript for various reasons, but still want to be able to use webauthn.

(4) My idea has a discrete "action" property. This allows both "login" and an explicit "logout" request.

There is no logout with webauthn

The browser needs to update the Authenticator with the result, and needs to maintain the login status of the RP.

The authenticators in webauthn doesn't know if their authentication succeed in RP side. The RP passes this information to the user, which then can choose what to do.

(6) It may be beneficial to allow the JSON configuration document hosted by the RP as an option. (accessible via HTTPS or FTPS, etc.) My idea is that it may be possible to use this document to perform user authentication for other applications beyond browsers and HTML. Imagine using the same Authenticator to login to other applications, online games (i.e. Steam, World of Warcraft), FTP, etc. As long as those other applications can parse JSON, make a call to the Authenticator, and create the desired transport data. (i.e. the existing JSON/JS data structure or a JSON Web Signature.) I imagine that eventually the Operating System will provide an API to connect to the Authenticator.

The example applications you've given would use the same configuration every time, that could be stored in application, which wouldn't need for developers to implement every possible config option, just those that they need, and wouldn't need an additional HTTP request.

@gartov
Copy link

gartov commented Jul 15, 2019

@ignaloidas You make some good points.

I think that a declarative HTML API should not be limited to the current webauthn, (i.e. To a specific version of the Credential Management Level 1, etc.) The API should be made more general than that. It should cover all the general use cases that a browser may need. This is to allow innovation in the future. (i.e. In what ways can the browser benefit the user when doing a user login?)

(1) Login should be done via a form.

I disagree. Requiring an HTML form in order to use webauthn is too limiting. What should the login system of the future look like? (in 10 years, etc.) The entire point of the new login system (webauthn, etc.) is to eliminate the user having to perform manual data entry. So why continue to use an HTML form and prompt people to perform data entry? What is the point of requiring websites to keep using old style HTML login forms, when 90 percent of the time, data entry will not be used. (people will not type anything into the form.)

If you really don't want to have an anchor send a POST, then a new HTML element could be created. (Create a "login" tag, etc. instead of using the "a" tag.) Or the anchor could perform a GET instead of a POST. etc. etc.

As a thought experiment, if it could be done, would it be better to have the "login/logout" button as a standard part of the browser chrome, and not as a visible element in the HTML page at all? (The HTML page would contain hidden attributes for the browser to interact with.) Instead of an anchor tag, how about using a "meta" tag, or a "link" tag in the header. (It might take years to train users how to do things that way, but special browsers or browser extensions could be created that do it. How about accessibility for blind users, etc. etc.)

(2) Webauthn does not have a logout.

OK. So webauthn lacks that use case or feature. I don't think it matters that much.

A declarative HTML API should provide a "logout" option. As using it gives the browser more abilities. It allows the browser to perform pre and post processing (before and after the logout) and figure out the current login state at the RP.

Most RP websites currently have a "logout" option. (As an anchor in the HTML page.) The proposed change is that the RP should use an API that explicitly informs the browser of the RP's intent to perform a logout. (i.e. The browser gets to perform pre and post processing.)

Also, the "action" property allows other actions besides "logout". For example, "prove-user-presence", "change-credentials", etc.

(3) The API should inform the browser of the success/fail of the login request.
The resulting RP login state should be maintained by the browser.

(This allows browsers to provide a "logoutAll" button and other features like that.)

OK. Webauthn does not inform the Authenticators if the login action succeeded or not. navigator.credentials.store does not work for PublicKeyCredentials. (It always returns an error.) However, this may not matter that much.

Some sort of response is still required from the RP to the browser. I think that: (1) The response should be caught by the user authentication code in the browser. So that the browser can perform post-processing after a successful login. (Even if webauthn does not do post-processing, the browser needs to provide the hook for it. For browser UI updating, browser extensions, etc.) (2) The browser should be able to tell if the response signals a login success or failure. The browser should keep track of the login status at the RP. (3) The response can also contain a redirect_uri. (Or the response can be a regular HTTP redirect response, as long as the other two criteria are met.)

Webauthn may not require a success/fail response or post-processing, but these traits are desirable for other use cases. The browser may want to do post-processing for some other reason (accessibility, visual cues in browser chrome, etc.), or other protocols may require post-processing. (Using a "password" Credential Management Level 1. etc. Also, by using new properties in the JSON, the RP can declare support for some other standard, and the browser can choose to use that standard.)

The idea is to give browsers the information about the RP login status, so as to allow browsers to innovate and automate as much as possible and provide more features for their users. Browsers are user agents, they act for the benefit of the user. They should be provided basic login information so as to allow them to do more for their user. (Such as to remember the login/logout status of RPs.)

For example, it would probably be a useful thing for browsers to have an easy way for the user to "logout from the browser". To close (or at least hide) all the current tabs that are logged in and may display sensitive information. Closing the browser will work, but should that be the only option? Would it not be convenient for the user to be able to close (or temporarily hide) all sensitive information in their browser in some situations? (If the user goes to the bathroom, if the browser is not used for an hour, etc.) Advanced users could use a browser setting to choose what they want to happen, etc.

I think it would a bad thing to not provide browsers with the explicit login status of the RP. This would limit browsers, so that when they innovate, they have to guess as to the RP login status.

(4) Using the JSON document for other applications.

Sounds good, a separate JSON document may not be needed for these cases at all. OK.

As a thought, if the URL for JSON document was moved to a "meta" or "link" element in the header, maybe that would allow HTTP/2 to deliver it along with all the other documents as part of the page load. That would cut out the additional HTTP request.

@emlun
Copy link
Member

emlun commented Jul 16, 2019

@Garnac What you're proposing is a much more ambitious vision for a standard web-wide sessions framework, which is way out of scope for the current WebAuthn working group. Even if limited to just emulating the JS API with declarative HTML, a completely new interface won't even be seriously considered by the WG. If we can make it a minimal addition that reuses as much as possible from the JS API, then maybe.

@ddevault
Copy link

In my opinion, the webauthn working group has betrayed its mission in the pursuit of shiny things if it does not build an option which doesn't require JavaScript.

@equalsJeffH
Copy link
Contributor

I agree with @emlun. Crafting a declarative HTML-based means to utilize the webauthn/fido protocol is "possible" and might be something to do down the road after we've gathered further deployment experience. We ought to keep this issue "around" as food for thought, perhaps in a "Futures" milestone.

@yackermann
Copy link
Contributor

@ignaloidas I don't see how would you make FIDO2 work without JS. It is requires four steps:

  • Get challenge, Send it to the authenticator, get response, send challenge back to the RP.

So it seems to be an overkill to create one declarative HTML API for something like that.

@ignaloidas
Copy link
Author

ignaloidas commented Aug 8, 2019

@herrjemand To break up:

  1. Get challenge. Server provides the challenge in HTML.
  2. Send it to the authenticator. Browser does that based on HTML document.
  3. Get response. Again, browser does that.
  4. Send challenge back to RP. Again, browser can do that, I guess by using form submission or something like that.

Noting that browser already kind of does 2 and 3 by itself(it is abstracted under JS API) there isn't any real stoppers for this.

@equalsJeffH
Copy link
Contributor

mentioned on 27-Nov-2019 call: @jcjones noted that perhaps the "pluggable authn mechs" being discussed in the QUIC IETF WG are potentially a means to ultimately address the anticipated use cases here?

@jcjones also noted that browser engines have been re-architected of late (eg gecko) such that accomodating functionality like is proposed here in the "DOM layer" would be difficult.

@gartov
Copy link

gartov commented Nov 28, 2019

I think that a QUIC "pluggable authn" is along the same lines as what I was thinking about.

@equalsJeffH can you provide a link or some more information on the "pluggable authn mechs" discussion?

If the authentication happens in the QUIC or HTTP/3 protocol, then that means that an HTML form is NOT used to do authentication. The authentication method is decoupled from the user's interactions with the HTML page. (The browser does not have to wait for the user to click an anchor or submit a form.) The browser may still interact with the user, for example to ask the user which credential or login to use with the RP, but this interaction is between the browser and the user, it is not a custom interaction between the RP and the user. (Performed by the RP's web page, including custom HTML and javascript.)

This is great. It gives the browser the tools it needs to innovate.

For browsers and websites that do not support xxx new technology (QUIC, etc.), there should be an easy way to shoehorn the user authentication into existing technologies. (i.e. into HTTP or HTML, etc.)

The HTML changes to allow for user authentication should NOT require the use of an HTML form, nor an HTML anchor. Although a form or anchor can ALSO be included in the HTML. For cases where an RP wants to provide custom actions (i.e. a redirect uri, etc.) or an on-page login interface for users. It allows an RP to provide users a more familiar interface. (i.e. users can login by interacting with the HTML page.)

The "pluggable" user authentication settings data (i.e. the webauthn settings) should be put into a special non-visible element in the HTML page header. Probably a meta tag or a link tag. (Alternately, the webauthn settings can be put in a separate JSON file on the server, and the meta or link tag can reference that file. In the href attribute, etc.) This allows the browser to retrieve the webauthn settings and perform webauthn (or some other future protocol) without requiring javascript, or that the user interact with the RP through the HTML page, etc.

Each user authentication method would need to define its own settings in the HTML. (A "pluggable" user authentication system needs to support multiple user authentication methods. i.e. other options besides "webauthn". The methods could be called a "protocol", or an "RP service", etc.)

Example:

<link rel="capis-act" href="/user_auth_config.json" capis-config='
"rpInfo": {
  "displayName": "ACME Corporation",
  "siteLogo": "/images/logo.png"
},
"serviceList": [
  {
    "protocol":"webauthn",
    "endpoint": {"http_method":"POST",
      "uri_address":"/exampleapp/webauthn/finish_assertion",
      "parameterList": [{"name":"user_auth_param", "type":"JSON"}],
      "assertion_format": "assertion format1",
      "creation_format": "creation format1"
    },
    "customData": {
      "exampleapp_request_id":"3a74f2b4-cff0-4f50-8076-2f5532a0d6f3"
    },
    "challenge": "WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs",
    "assertionOptions": {
      "propertyName": "assertionInfo",
      "publicKey": {
        "challenge_old": "WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs",
        "allowCredentials": [{ "type": "public-key", "id": "Pzy_hxuR849qZbLAE8Vr4U3KPiIN6W10ssJOR55BMZQ",
            "transports": ["usb", "nfc", "ble", "lighning", "internal"] }],
        "userVerification": "preferred",
        "timeout": 120000,
        "extensions": {"loc": false }
      }
    },
    "creationOptions": {
      "propertyName": "creationInfo",
      "publicKey": {
        "challenge_old": "WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs",
        "rp_old": {"name": "ACME Corporation"},
        "user": {"id": "bYq3-Uk0J3xMt", "name": "[email protected]",
          "displayName": "Alex P. Mueller"},
        "pubKeyCredParams": [{ "type": "public-key", "alg": -7 }, 
            { "type": "public-key", "alg": -257 }],
        "authenticatorSelection": {
          "authenticatorAttachment": "platform",
          "residentKey": "required",
          "requireResidentKey": true,
          "userVerification": "preferred"
        },
        "timeout": 360000,
        "attestation": "none",
        "excludeCredentials": [],
        "extensions": {"loc": false }
      }
    }
  }
],
"nonceIssueTime":1533064012,
"nonce":"WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs" '/>

Later in the HTML.

<a href="/fallback/login.html" rel="capis-act" capis-config=' "serviceAction":"webauthn/login", "redirect_uri":"/page2.html" '>Login Here</a>

Notes:

  1. The JSON in the "link" tag should be able to contain settings for multiple services. (user authentication or other types.) So it uses a "serviceList". This also makes it extensible for the future.
  2. The "endpoint" property specifies everything about the RP endpoint, that receives the service request. (The URI, http_method, etc.)
  3. The HTML page should contain settings for BOTH webauthn.get and webauthn.create. (Called "creationOptions" and "assertionOptions" in the example.) This gives the browser the most flexibility. (i..e It can choose to use webauthn.get or webauthn.create, in response to user demands, etc..)
  4. Moved some webauthn properties around. Changed the old property name to end in "_old". The "challenge" was moved outside of both "creationOptions" and "assertionOptions", so it can be used by both. etc. (Also, "rp_old")
  5. There could be some properties that are useful for all protocols. The "nonce" and a "nonceIssueTime" may be useful. (Can use the "nonce" instead of the webauthn "challenge".) HTML5 has settled on using the name "nonce".
  6. The anchor will trigger the browser to perform the specified "service action". The JSON property "serviceAction":"webauthn/login", informs the browser to use the "webauthn" service to perform a "login". It does not tell the browser to do either the "webauthn_get" or the webauthn_create" action. The browser can make that decision on its own.
  7. How the browser gets a new user_id. Either the RP's response to a "webauthn.create" should include a new "user_id". Or a new user_id can be reserved by the RP for the current connection/session/user, and provided in the webauthn settings. in base64url encoding. (i.e. creationOptions.user.id = "bYq3-Uk0J3xMt")
  8. The "customData" property contains any data that the RP wants to be sent back to it. The browser is to copy the entire contents of the "customData" property into the request sent to the RP. (i.e. into the POST.) The "exampleapp_request_id" is an application-specific reference to server-side state.
  9. The "assertion_format" of the endpoint specifies the transport format the endpoint requires for a webauthn.get request. (There should be a default value, so the example could remove that property.)
  10. The POST to the RP will (most likely) have at least one parameter that is a JSON object.
    The "assertionInfo" may be sent as separate parameter. (The parameter may be given a different name. By using "propertyName", etc.)

Example POST sent to the RP. "assertionInfo" may be sent as a separate parameter.

user_auth_json={
  "serviceAction": "webauthn.get",
  "customData":{"exampleapp_request_id":"3a74f2b4-cff0-4f50-8076-2f5532a0d6f3"},
  "assertionInfo": {
    "credentials": [{ // Allow multiple credentials, for a future extension?
      "id":"K3xM080fiCDCkv412SdQ6--982rRf9i6NtDY0Jkv-AJZaCH9-MNs-ijV2y2OXKbxjzD3rXR05rmGF_jGQnXyPQ",
      "response":{
        "authenticatorData":"xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cBAAAANQ==",
        "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJwa...",
        "signature":"MEUCIHw..."
      },
    }],
    "clientExtensionResults":{},
    "transports":["usb"]
  }
}

@tigt
Copy link

tigt commented Dec 1, 2019

I doubt browsers want to revive the <keygen> element, but it addressed a lot of design decisions brought up in this thread. A strawman using only already-standardized HTML could look like:

<form method="post" action="/exampleapp/webauthn/finish_assertion">
  <input name="user" autocomplete="username">

  <keygen name="webauthn"
    required
    keytype="webauthn-public"
    keyparams="alg -7"
    challenge="WwdPrNbUcI1034qZ9LYks32ZPezDLZSieFI9f6NaZZs">
    <!-- fallback for non-implementing browsers goes here -->
  </keygen>

  <input type="hidden" autocomplete="webauthn-userid">
  <input type="hidden" name="exampleapp_request_id" value="3a74f2b4-cff0-4f50-8076-2f5532a0d6f3">
</form>

The required attribute is reused for the WebAuthN API’s userVerification: 'required'. hidden could be used for userVerification: 'discouraged', and 'preferred' would be the default.

Multiple credentials as mentioned by @Garnac could be implemented with multiple elements.

Of course, this strawman lacks a lot of the discussed functionality, but I think it’s worth learning from the Web of yesteryear for how one might implement webauthn without JavaScript.

@ignaloidas
Copy link
Author

@equalsJeffH where could I find more info on it?

I don't think QUIC specific technology would address all the use cases. Namely Tor wouldn't be able to use it, as it doesn't support UDP that QUIC uses. Personally I don't think that authentication should be coupled with transport mechanisms, as that would prevent using it via alternative methods of transport, since not all of them might not be available. E.g. UDP is currently quite often blocked in networks. Now QUIC might change this, but it would take a while for change to happen. Thus a bizzarre situation might happen where both a server and the browser support an auth mechanism, but can't use it because something in between doesn't.

@DavidSchinazi
Copy link

Hi folks, I work on HTTP/3 and QUIC for Chrome. I'm jumping in to answer questions about QUIC authentication.

There hasn't been much discussion of "pluggable authn mechs" in the IETF QUIC working group. More specifically, the IETF QUIC WG is currently very focused on shipping HTTP/3 with TLS 1.3. That said, the QUIC transport and authentication documents were intentionally split so that one could "plug" a new authentication/key exchange scheme as a replacement for TLS. The one example I know of is nQUIC, but even that is a research project that was never deployed in production.

Another area of research that is worth noting is the integration of a PAKEs with TLS. One prominent candidate is OPAQUE: see OPAQUE-TLS. The idea there is to allow proof of password ownership to be transmitted at the TLS layer without actually transmitting the password. This also provides bindings between the exchange and the TLS connection, making man-in-the-middle proxies' lives harder.

So if Webauthn were to pursue authentication at the transport layer, then I would strongly recommend making this at the TLS layer instead of QUIC, because it would allow working over networks that block UDP (or server deployments that aren't ready for QUIC yet), and also because it would not require reinventing a new authentication / key exchange scheme, which is fraught with peril.

All that said, I'm not sure moving webauthn to the transport is necessarily the right idea. Almost all Web/HTTP authentication has been historically performed at the application layer; and, if we were to change it, it should probably involve a bigger effort that also tries to do this for passwords. But that might take copious amounts of time and might derail the conversation about Javascript...

@koteisaev
Copy link

I surprised that WebAuthn has ONLY javascript API, without a pure html version.
But I supsect that this is because a lot of aspects is required to be defined, especially at scope like "developers taste".
For me, this can be done with using these:

  1. link with rel="auth/webauthn" href="url-to-config" name="relPartyConfigurationName">
  2. script type="application/json+webauth.relPartyConfiguration" id "relPartyConfigurationName" that can be used by sites who prefer to pre-render this kind of settings.
  3. script type="application/json+webauth.registrationOptions" id="registrationOptions" to store al this pack of options that necessary for webauth registration API call
  4. script type="application/json+webauth.loginOptions" id="registrationOptions" to store al this pack of options that necessary for webauth registration API call
  5. in webforms the input of type="authentication" scheme="webauthn" verb="register" configuration="registrationOptions" and similar for "authentication" verb, but with "maxage" attribute to represent how old last user attestation can be for authentication for form send request.

What I wanted to avoid or make optional, is having JSON blobs at attribute values, and allow prerendering old dynamic stuff, like challenge for registration forms.

using unsupported type for input will allow to fallback to js scenario in case if html-only form auth does not supported.

In the "authentication" input attribute "scheme" can be various, in case if e. g. scheme like webauthn/2.0 will emerge, or even "basic" if it is suitable for context/project.

Any no-js solution for advanced authentication require following - work with W3C to get consensus on how represent complicated auth schemes, like webauthn in html.

Another question - how to guarantee for example, that anything that has been considered as non-public, downloaded since registration/authentication flow successfully completed, would be authenticated with this public key auth? Like if user requests a protected page, server returns 401, and what server must say in challenge details to kick in webauthn flow? Should server send that fancy JSON as body for 401 response?

Ultmate flexibity is not just avoiding JS, but option to not having even html at all, and use the public key created in webauthn flow to authenticate all or "protected" requests within session regardless of usage of JS or even html. Webauthn concept actually allow to introduce the ability to register user without JS and html in general but this may be beyond the scope of discussion.

@emlun
Copy link
Member

emlun commented May 25, 2020

prerendering old dynamic stuff, like challenge for registration forms.

You should never reuse a challenge, it should be uniquely generated for each registration/authentication ceremony. See §13.4.1. Cryptographic Challenges
(and pardon me if I misinterpreted what you meant).

@koteisaev
Copy link

prerendering old dynamic stuff, like challenge for registration forms.

You should never reuse a challenge, it should be uniquely generated for each registration/authentication ceremony. See §13.4.1. Cryptographic Challenges (and pardon me if I misinterpreted what you meant).

Nice catch!
The options object can have not "challenge", but "challengeSource (url), expected to return e. g. application/octet-stream with raw bytes for challenge, and can have some restrictions like request CORS restrictions must apply, and require some rule for presense of referrer within same domain..

What do you think about "webauthn without html" scenario, especially
Like if user requests a protected page, server returns 401, and what server must say in challenge details to kick in webauthn flow? Should server send that fancy JSON need to kick webauth login flow as body for 401 response?
Of course this will require sending public key generated from credentials to work in full "no-html" scenario after login via webauthn was made, for all requests within related origin.... Which can replace cookies used for keeping login sessions today....

@MasterKale
Copy link
Contributor

Hey @tcannonfodder, I've been on PTO for the last two weeks and won't get back into work mode till Monday. I'll review the current state of this issue next week to refresh my memory and more meaningfully respond to you ✌️

@arianvp
Copy link

arianvp commented Dec 9, 2023

Not sure if it's particularly helpful for this issue. But I managed to get Webauthn working without XHR; but using a multipart form submission instead. This way I don't need to pull in any libraries for serializing binary data to json.
It at least kind of keeps the semantic html. And whatever non-js version of Webauthn should probably have this as behaviour in some way or another.

<!doctype html>
<form action="login" data-challenge="{{ .Challenge }}" method="post" enctype="multipart/form-data">
    <input type="text" name="username" autocomplete="username webauthn">

    <input type="hidden" name="type">
    <input type="hidden" name="id">
    <input type="file" name="clientDataJSON" accept="application/json" hidden>
    <input type="file" name="authenticatorData" accept="application/octet-stream" hidden>
    <input type="submit" value="Submit">
</form>

<script type="module">
    function submitCredential(credential, form) {
        form.elements.namedItem('type').value = credential.type;
        form.elements.namedItem('id').value = credential.id;
        const clientDataJSON = new DataTransfer();
        clientDataJSON.items.add(new File([response.clientDataJSON], 'clientDataJSON'), { type: 'application/json' });
        form.elements.namedItem("clientDataJSON").files = clientDataJSON.files;
        if (response instanceof AuthenticatorAttestationResponse) {
            const attestationObject = new DataTransfer();
            attestationObject.items.add(new File([response.attestationObject], 'attestationObject'), { type: 'application/cbor' });
            form.elements.namedItem("attestationObject").files = attestationObject.files;
        }
        if (response instanceof AuthenticatorAssertionResponse) {
            const authenticatorData = new DataTransfer();
            authenticatorData.items.add(new File([response.authenticatorData], 'authenticatorData'), { type: 'application/octet-stream' });
            form.elements.namedItem("authenticatorData").files = authenticatorData.files;
        }
        form.submit();
    }

    const login = document.forms.namedItem('login');
    const challengeLogin = Uint8Array.from(atob(login.dataset['challenge']), c => c.charCodeAt(0));

    login.onsubmit = async (event) => {
        event.preventDefault();
        const credential = await navigator.credentials.get({
            publicKey: { challenge: challengeLogin, userVerification: 'required', allowCredentials: [] },
        });
        submitCredential(credential, form);
    };

    if (PublicKeyCredential.isConditionalMediationAvailable && await PublicKeyCredential.isConditionalMediationAvailable()) {
        const credential = await navigator.credentials.get({
            mediation: 'conditional',
            publicKey: { challenge, userVerification: 'required', allowCredentials: [] },
        });
        submitCredential(credential, login);
    }


</script>

@alcinnz
Copy link

alcinnz commented Dec 10, 2023

I'm excited to prototype an implementation over the coming weeks as a strawman!

I'll be adding it to: https://git.argonaut-constellation.org/~alcinnz/bureaucromancy

There's significant architectural differences between how I implement webforms vs most other browsers, but that should help progress this issue!

@rlin1
Copy link
Contributor

rlin1 commented Dec 12, 2023

Note: there is an initiative to add FIDO to the TLS stack as an alternative to TLS client authentication. Wouldn't that address the same use case? It wouldn't need JS.

@ryanhiebert
Copy link

@rlin1 IIUC, I don't think it would address my typical use-case, which wants to have some pages accessible unauthenticated and others authenticated, and some available both ways. Client authentication would need to happen globally or perhaps per-resource, right, and be a total block to the resource?

@rlin1
Copy link
Contributor

rlin1 commented Dec 12, 2023

@rlin1 IIUC, I don't think it would address my typical use-case, which wants to have some pages accessible unauthenticated and others authenticated, and some available both ways. Client authentication would need to happen globally or perhaps per-resource, right, and be a total block to the resource?

you could require it based on the URL - and still provide access to the same resource if you wanted.

@MasterKale
Copy link
Contributor

MasterKale commented Dec 12, 2023

To be clear, when I think of "use WebAuthn without JavaScript" I think of "enabling the use of a <form> and a <button type="submit"> to initiate WebAuthn ceremonies." It's the what of the contents of that <form> that is the most compelling line of thought for me here. Why can't calling WebAuthn be as simple as it is to create a standard HTML form to collect username+password?

@tcannonfodder
Copy link

tcannonfodder commented Dec 12, 2023

To be clear, when I think of "use WebAuthn without JavaScript" I think of "enabling the use of a <form> and a <button type="submit"> to initiate WebAuthn ceremonies." It's the what of the contents of that <form> that is the most compelling line of thought for me here. Why can't calling WebAuthn be as simple as it is to create a standard HTML form to collect username+password?

1000% co-sign; this should be the way it is! 💪

@pabs3
Copy link

pabs3 commented Dec 12, 2023 via email

@MasterKale
Copy link
Contributor

Moving this to "Futures" as potential work for L4.

@arianvp
Copy link

arianvp commented Dec 21, 2023

I've started experimenting with a Form-Associated Custom Element (FACE) that allows you to use Webauthn with "just" HTML.

FACE is cool as you can define how the element contributes to form submission. This makes the element I define function very similar to the old <keygen>. It just send the clientDataJSON and signature as a multipart file.

Of course you need the javascript to define the element. But can serve as a playground to find the right html interface

Example of conditional UI

<script type="module" src="./index.js"></script>

<!-- this form either redirects to  login.html?username=blah to  do a modal flow -->
<!-- Or it triggers the webauthn autocomplete and triggers the form below -->
<form method="get" action="login.html">
    <label for="username">User name</label>
    <input type="text" name="username" id="username" autocomplete="username webauthn">
    <input type="submit" value="Log in">
</form>
<!-- this form gets triggered by the `input` element marked `webauthn` -->
<!-- encodes the signature and clientDataJSON as  multipart/form-data -->
<form method="post" action="login.html" enctype="multipart/form-data">
    <public-key-credential challenge="AAAA" name="credential" conditional />
</form>

Example of modal UI:

<script type="module" src="./index.js"></script>
<form method="post" enctype="multipart/form-data">
    <public-key-credential 
        name="credential" 
        allow-credentials="h1OZg6bnPuktiGIlE39YwY9VRTw" 
        challenge="AAAA" />
    <input type="submit" value="Login with Passkey">
</form>

https://github.com/arianvp/public-key-credential-element/blob/main/index.ts

NOTE: There seems to be some bugginess in Safari where the <public-key-credential conditional> element can not be in the same parent form as the <input type=text autocomplete=webauthn> input or the autocomplete UI will not trigger and the conditional promise never resolves. Feel this is some kind of weird bug

@dwaite
Copy link
Contributor

dwaite commented Dec 21, 2023

Since WebAuthn L3 defines a JSON form for the request and response messages, I would be motivated to use this rather than defining a new decomposition of WebAuthn API messages.

I could readily imagine this as JSON encoded in an attribute on an object or new tag, or as a reference to a <script type="text/javascript" id="webauthn-request"> { "public-key": { ... }} </script>, although the second may have CSP ramifications I'm not familiar with.

@arianvp
Copy link

arianvp commented Dec 21, 2023

I don't really see the benefit of that. On the other side as RP you're gonna have to change that base64 string to a binary again anyway. Why not cut the middleman and send the binary signature as a binary directly?

Using form elements for sending data seems to be the most natural way to express these things in my opinion. All the infrastructure for it is already there. Lets not reinvent the wheel.

@ryanhiebert
Copy link

@arianvp I'm still a fairly naive bystander, but what you're proposing looks really good to me, and very much along the lines of what I was hoping for. That (IIUC) the custom element you've defined would be an empty unknown element if JS isn't working or the script is missing is excellent for progressive enhancement. This, or something close to it, it the kind of API I'd like to see, from my current understanding of the problem space.

@sycamoreoak
Copy link

It has been a year since the last post on this, how far are we from HTML-only webauthn? What remains to be done? Why is there any opposition, if any?

@MasterKale
Copy link
Contributor

The focus lately has been on finalizing L3 and getting it to Recommendation status ("finally releasing L3".) Once we get through this we can start looking at items like this one marked "L4 Futures" that weren't able to be addressed in L3. That's the current blocker to thinking about how to possibly address this issue.

@MasterKale
Copy link
Contributor

From the WG call today: the biggest challenge right now is identifying a large enough number of users who would be positively impacted by the addition of non-JS support for passkey authentication to justify hypothetical implementation by browser vendors. Those of us who cross-participate in the FIDO Alliance can take this up with potentially interested WG's over there to see if there's been any communicated at-scale desire for this capability.

@selfissued
Copy link
Contributor

Here's an example of a 15-year-old no-longer-deployed non-JavaScript invocation method:

11.1 Syntax Alternatives: OBJECT and XHTML tags

@noloader
Copy link

From the WG call today: the biggest challenge right now is identifying a large enough number of users who would be positively impacted by the addition of non-JS support for passkey authentication to justify hypothetical implementation by browser vendors. Those of us who cross-participate in the FIDO Alliance can take this up with potentially interested WG's over there to see if there's been any communicated at-scale desire for this capability.

Based on search results, it looks like between 1% and 3% of users do not have JavaScript enabled. That includes users who make the choice by installing browser extensions like NoScript; and corporate users who experience a policy that disables JavaScript.

The 3% is an important threshold, in my opinion. That's the critical mass to support a feature. For example, in the US, the IRS would support a browser with 3% market share. It was a big deal back in the 1990's. I don't know what the IRS policy is nowadays (but its probably a lesser concern since the browser consolidations and evolution that have organically happened over time).

Also see discussions like https://www.mediawiki.org/wiki/No-JavaScript_notes. And there will be folks who won't comment on this report because they don't want a GitHub account due to Microsoft. There's at least one on debian-users mailing list.

@arianvp
Copy link

arianvp commented Jan 23, 2025

I think this is the wrong train of thought to think of end-users directly .You can't reach all your end-users if RPs are having challenges with implementing the technology. The other angle is that JavaScriptless implementation makes passwordless auth ubiquitous for developers. Meaning that more websites will implement it and thus we'll reach more end users with this technology.

I envision a future where passkey auth is a single function call away in PHP just like it is right now with password_crypt and form based password login.

Currently people who have perfectly secure but form based logins in php suddenly need to start thinking of adding JS to their stack instead of just having the payload that goes over the wire change. Imagine if we can upgrade a classic form to a WebAuthn form with just a line of code. It'd be really neat

The interesting metric in my opinion is:

How many log in forms are currently classic <form>s and how many are we going to burden with having to add JavaScript?

This data can be collected to make an educated guess. In my experience many if not most login forms to date are JavaScriptless and session-based. We're telling all people to change this.

@rlin1
Copy link
Contributor

rlin1 commented Jan 23, 2025

Researchers have experimentally added FIDO support to TLS as an alternative client authentication method. For example, see https://sar.informatik.hu-berlin.de/research/publications/SAR-PR-2021-02/SAR-PR-2021-02_.pdf.

Obviously that would require the TLS server to support FIDO and the TLS client (i.e. Browser) to do so as well.
This approach addresses the channel binding aspect as well as the inconsequential nature of the token binding approach (see #2239 (comment)).

Would that address the concerns of using JavaScript?

@sycamoreoak
Copy link

sycamoreoak commented Jan 23, 2025

Although the percent of all browser clients blocking javascript is relevant, another question would be what percent of high computer literacy web users (eg, programmers, sysadmins, etc) block javascript? It's probably pretty high.

@kevATin
Copy link

kevATin commented Jan 23, 2025

Although the percent of all browser clients blocking javascript is relevant, another question would be what percent of high computer literacy web users (eg, programmers, sysadmins, etc) block javascript? It's probably pretty high.

I would guess that the percentage of users who block javascript would be significantly higher if essential web technologies weren't so dependent on javascript nowadays.
So I consider using the rarity of users disabling javascript as justification to not make the feature work without javascript to be a bit disingenuous.

@tcannonfodder
Copy link

tcannonfodder commented Jan 23, 2025

I think this is the wrong train of thought to think of end-users directly .You can't reach all your end-users if RPs are having challenges with implementing the technology. The other angle is that JavaScriptless implementation makes passwordless auth ubiquitous for developers. Meaning that more websites will implement it and thus we'll reach more end users with this technology.

Currently people who have perfectly secure but form based logins in php suddenly need to start thinking of adding JS to their stack instead of just having the payload that goes over the wire change. Imagine if we can upgrade a classic form to a WebAuthn form with just a line of code. It'd be really neat

[...] In my experience many if not most login forms to date are JavaScriptless and session-based. We're telling all people to change this.

Another important note: every single browser, client, and device has Javascript disabled until it is parsed, read, and bug-free.

WebAuthn can't compete with the ergonomics & reliability of passwords, "magic links", or any other approach that can work through basic HTTP calls (via form submissions and browser requests). It's a non-starter for apps that work in slow data environments, flakey network connections; since users will not be able to log in if JS breaks.

Deciding not to add native, browser-based code for performing a WebAuthn handshake to a series of URLs is a bad idea, IMO. It tees up the feature to never be fully production-ready, because of the realities of how JS interacts in the browser.

Put another way: browser advancements are letting us write more and more of our apps with less & less javascript. Authentication should follow that same ethos. 100% of users will be positively impacted by the addition of non-JS support for passkey authentication.

@noloader
Copy link

Although the percent of all browser clients blocking javascript is relevant, another question would be what percent of high computer literacy web users (eg, programmers, sysadmins, etc) block javascript? It's probably pretty high.

I would guess that the percentage of users who block javascript would be significantly higher if essential web technologies weren't so dependent on javascript nowadays. So I consider using the rarity of users disabling javascript as justification to not make the feature work without javascript to be a bit disingenuous.

++.

Related, when we encounter JavaScript running on a login page at $dayjob for internally developed apps, we file a security related issue. We require the business owner to accept the risk.

I don't think a typical web user understands the risk, so they should not be required to accept it.

@ignaloidas
Copy link
Author

Researchers have experimentally added FIDO support to TLS as an alternative client authentication method. For example, see https://sar.informatik.hu-berlin.de/research/publications/SAR-PR-2021-02/SAR-PR-2021-02_.pdf.

Obviously that would require the TLS server to support FIDO and the TLS client (i.e. Browser) to do so as well. This approach addresses the channel binding aspect as well as the inconsequential nature of the token binding approach (see #2239 (comment)).

Would that address the concerns of using JavaScript?

While this is pretty cool, I don't think this is a good way to get more webauthn adoption. It would essentially move authentication into the TLS termination layer, which in most common web app deployments is a reverse proxy that does not handle authentication at all. Handling webauthn in HTML would meet most RPs where they are at, while handling it in TLS would require quite a lot of changes for them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests