From ac74c0451569fda8d140ebdb185f0ec8f6ec6f24 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 10:24:13 -0700 Subject: [PATCH 01/14] Switch to Markdown --- source/index.md | 7 +++++++ source/{index.rst => index.rst.old} | 0 2 files changed, 7 insertions(+) create mode 100644 source/index.md rename source/{index.rst => index.rst.old} (100%) diff --git a/source/index.md b/source/index.md new file mode 100644 index 0000000..f928c2e --- /dev/null +++ b/source/index.md @@ -0,0 +1,7 @@ +# py_webauthn documentation + +Add your content using ``reStructuredText`` syntax. See the +[reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html) +documentation for details. + +Hello from Duo! diff --git a/source/index.rst b/source/index.rst.old similarity index 100% rename from source/index.rst rename to source/index.rst.old From 7fee8684c00e75c85e9338794dad74473edeb4c3 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:07:21 -0700 Subject: [PATCH 02/14] Expand on intro --- source/index.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/source/index.md b/source/index.md index f928c2e..464077f 100644 --- a/source/index.md +++ b/source/index.md @@ -1,7 +1,22 @@ -# py_webauthn documentation +# py_webauthn -Add your content using ``reStructuredText`` syntax. See the -[reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html) -documentation for details. +[![PyPI](https://img.shields.io/pypi/v/webauthn.svg)](https://pypi.python.org/pypi/webauthn) [![GitHub license](https://img.shields.io/badge/license-BSD-blue.svg)](https://raw.githubusercontent.com/duo-labs/py_webauthn/master/LICENSE) ![Pythonic WebAuthn](https://img.shields.io/badge/Pythonic-WebAuthn-brightgreen?logo=python&logoColor=white) -Hello from Duo! +A Python3 implementation of the server-side of the [WebAuthn API](https://www.w3.org/TR/webauthn-2/) focused on making it easy to leverage the power of WebAuthn. + +This library supports all FIDO2-compliant authenticators, including security keys, Touch ID, Face ID, Windows Hello, Android biometrics...and pretty much everything else. + +## Installation + +This module is available on **PyPI**: + +`pip install webauthn` + +## Requirements + +- Python 3.8 and up + +```{toctree} +:maxdepth: 2 +hello +``` From f0512266eabf0df8bcb25d92365afa7eee7cf6d1 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:07:26 -0700 Subject: [PATCH 03/14] Configure theme --- source/conf.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/conf.py b/source/conf.py index 6708802..552187a 100644 --- a/source/conf.py +++ b/source/conf.py @@ -26,6 +26,21 @@ html_theme = "alabaster" html_static_path = ["_static"] +html_sidebars = { + "**": [ + "about.html", + "searchfield.html", + "navigation.html", + "relations.html", + ] +} +html_theme_options = { + "github_user": "duo-labs", + "github_repo": "py_webauthn", + "description": "Pythonic WebAuthn 🐍", + "github_button": True, + "show_powered_by": False, +} # -- Extension Configuration - MyST Parser From f553defaa977f1b73b7f17a1ff487da88aaa1f56 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:07:35 -0700 Subject: [PATCH 04/14] Add Hello doc for navigation --- source/hello.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 source/hello.md diff --git a/source/hello.md b/source/hello.md new file mode 100644 index 0000000..fec5601 --- /dev/null +++ b/source/hello.md @@ -0,0 +1 @@ +# Hello From cf6b2070a539a92b144b98291b42a35d8e2b4c07 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:47:18 -0700 Subject: [PATCH 05/14] Create some subpages --- source/foobar.md | 5 +++++ source/hello.md | 6 ++++++ source/index.md | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 source/foobar.md diff --git a/source/foobar.md b/source/foobar.md new file mode 100644 index 0000000..821685d --- /dev/null +++ b/source/foobar.md @@ -0,0 +1,5 @@ +# Foobar + +## Fizz + +## Buzz diff --git a/source/hello.md b/source/hello.md index fec5601..fed373f 100644 --- a/source/hello.md +++ b/source/hello.md @@ -1 +1,7 @@ # Hello + +This is some text + +## Level 2 + +### Level 3 diff --git a/source/index.md b/source/index.md index 464077f..faf0c35 100644 --- a/source/index.md +++ b/source/index.md @@ -17,6 +17,9 @@ This module is available on **PyPI**: - Python 3.8 and up ```{toctree} +:hidden: :maxdepth: 2 + hello +foobar ``` From 84ab23526894fc2e179279b5964f8bafeccbb668 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:59:18 -0700 Subject: [PATCH 06/14] Remove old .rst file --- source/index.rst.old | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 source/index.rst.old diff --git a/source/index.rst.old b/source/index.rst.old deleted file mode 100644 index 7707b55..0000000 --- a/source/index.rst.old +++ /dev/null @@ -1,18 +0,0 @@ -.. py_webauthn documentation master file, created by - sphinx-quickstart on Mon Aug 12 18:17:35 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -py_webauthn documentation -========================= - -Add your content using ``reStructuredText`` syntax. See the -`reStructuredText `_ -documentation for details. - -Hello from Duo! - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - From 86ed7419bc5eb34cc2f34a20f31f755a42f3759e Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 12:59:24 -0700 Subject: [PATCH 07/14] Improve dev experience --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5249f95..d87233c 100644 --- a/Makefile +++ b/Makefile @@ -20,4 +20,4 @@ help: @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) livehtml: - sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + sphinx-autobuild --pre-build 'rm -rf build/' "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) From 2a94bc9c89090d1abd8211e92c80b97f34cc4940 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 14:21:13 -0700 Subject: [PATCH 08/14] Scaffold out basic docs structure --- source/authentication.md | 3 +++ source/foobar.md | 5 ----- source/hello.md | 7 ------- source/index.md | 6 ++++-- source/intro.md | 30 ++++++++++++++++++++++++++++++ source/passkeys.md | 3 +++ source/registration.md | 3 +++ 7 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 source/authentication.md delete mode 100644 source/foobar.md delete mode 100644 source/hello.md create mode 100644 source/intro.md create mode 100644 source/passkeys.md create mode 100644 source/registration.md diff --git a/source/authentication.md b/source/authentication.md new file mode 100644 index 0000000..d639fc3 --- /dev/null +++ b/source/authentication.md @@ -0,0 +1,3 @@ +# Authentication + +Coming Soon diff --git a/source/foobar.md b/source/foobar.md deleted file mode 100644 index 821685d..0000000 --- a/source/foobar.md +++ /dev/null @@ -1,5 +0,0 @@ -# Foobar - -## Fizz - -## Buzz diff --git a/source/hello.md b/source/hello.md deleted file mode 100644 index fed373f..0000000 --- a/source/hello.md +++ /dev/null @@ -1,7 +0,0 @@ -# Hello - -This is some text - -## Level 2 - -### Level 3 diff --git a/source/index.md b/source/index.md index faf0c35..ca86ac1 100644 --- a/source/index.md +++ b/source/index.md @@ -20,6 +20,8 @@ This module is available on **PyPI**: :hidden: :maxdepth: 2 -hello -foobar +intro +registration +authentication +passkeys ``` diff --git a/source/intro.md b/source/intro.md new file mode 100644 index 0000000..cca6d0d --- /dev/null +++ b/source/intro.md @@ -0,0 +1,30 @@ +# Intro + +The **py_webauthn** library exposes a small number of core methods from the `webauthn` module: + +- `generate_registration_options()` +- `verify_registration_response()` +- `generate_authentication_options()` +- `verify_authentication_response()` + +Two additional helper methods are also exposed: + +- `options_to_json()` +- `base64url_to_bytes()` + +Additional data structures are available on `webauthn.helpers.structs`. These dataclasses are useful for constructing inputs to the methods above, and for providing type hinting to help ensure consistency in the shape of data being passed around. + +## Assumptions + +Generally, the library makes the following assumptions about how a Relying Party implementing this library will interface with a webpage that will handle calling the WebAuthn API: + +- JSON is the preferred data type for transmitting registration and authentication options from the server to the webpage to feed to `navigator.credentials.create()` and `navigator.credentials.get()` respectively. +- JSON is the preferred data type for transmitting WebAuthn responses from the browser to the server. +- Bytes are not directly transmittable in either direction as JSON, and so should be encoded to and decoded from [base64url to avoid introducing any more dependencies than those that are specified in the WebAuthn spec](https://www.w3.org/TR/webauthn-2/#sctn-dependencies). + - See the [`WebAuthnBaseModel` struct](https://github.com/duo-labs/py_webauthn/blob/master/webauthn/helpers/structs.py#L13) for more information on how this is achieved + +The examples mentioned below include uses of the `options_to_json()` helper (see above) to show how easily `bytes` values in registration and authentication options can be encoded to base64url for transmission to the front end. + +The examples also include demonstrations of how to pass JSON-ified responses, using base64url encoding for `ArrayBuffer` values, into `parse_registration_credential_json` and `parse_authentication_credential_json` to be automatically parsed by the methods in this library. An RP can pair this with corresponding custom front end logic, or one of several frontend-specific libraries (like [@simplewebauthn/browser](https://www.npmjs.com/package/@simplewebauthn/browser), for example) to handle encoding and decoding such values to and from JSON. + +Other arguments into this library's methods that are defined as `bytes` are intended to be values stored entirely on the server. Such values can more easily exist as `bytes` without needing potentially extraneous encoding and decoding into other formats. Any encoding or decoding of such values in the name of storing them between steps in a WebAuthn ceremony is left up to the RP to achieve in an implementation-specific manner. diff --git a/source/passkeys.md b/source/passkeys.md new file mode 100644 index 0000000..1e2d6f4 --- /dev/null +++ b/source/passkeys.md @@ -0,0 +1,3 @@ +# Passkeys + +Coming Soon diff --git a/source/registration.md b/source/registration.md new file mode 100644 index 0000000..5eb6b62 --- /dev/null +++ b/source/registration.md @@ -0,0 +1,3 @@ +# Registration + +Coming Soon From 23c65c47cb631431aca63ae743b8fe43675baf75 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 15:13:19 -0700 Subject: [PATCH 09/14] Add example links to reg and auth --- source/authentication.md | 6 ++++++ source/registration.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/source/authentication.md b/source/authentication.md index d639fc3..2b2e840 100644 --- a/source/authentication.md +++ b/source/authentication.md @@ -1,3 +1,9 @@ # Authentication Coming Soon + +## Examples + +Examples of the use of `generate_authentication_options` and `verify_authentication_response` are available here: + + diff --git a/source/registration.md b/source/registration.md index 5eb6b62..941ddfa 100644 --- a/source/registration.md +++ b/source/registration.md @@ -1,3 +1,9 @@ # Registration Coming Soon + +## Examples + +Examples of the use of `generate_registration_options` and `verify_registration_response` are available here: + + From 262e22c3712556c4dc60d27edf45212854d71ba3 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 15:13:43 -0700 Subject: [PATCH 10/14] Rearrange config --- source/conf.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/source/conf.py b/source/conf.py index 552187a..5799948 100644 --- a/source/conf.py +++ b/source/conf.py @@ -24,8 +24,20 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "alabaster" html_static_path = ["_static"] + +# -- Theme configuration ----------------------------------------------------- +# https://alabaster.readthedocs.io/en/latest/customization.html + +html_theme = "alabaster" +html_theme_options = { + "github_user": "duo-labs", + "github_repo": "py_webauthn", + "description": "Pythonic WebAuthn 🐍", + "github_button": True, + "show_powered_by": False, + "show_relbar_bottom": True, +} html_sidebars = { "**": [ "about.html", @@ -34,15 +46,9 @@ "relations.html", ] } -html_theme_options = { - "github_user": "duo-labs", - "github_repo": "py_webauthn", - "description": "Pythonic WebAuthn 🐍", - "github_button": True, - "show_powered_by": False, -} -# -- Extension Configuration - MyST Parser +# -- Extension Configuration - MyST Parser ----------------------------------- +# https://myst-parser.readthedocs.io/en/latest/configuration.html myst_enable_extensions = [ "colon_fence", From fd21314cbdf20ddc4992bcb606d7a2496980fad6 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 15:13:54 -0700 Subject: [PATCH 11/14] Add customer CSS for theme overrides --- source/_static/styles.css | 9 +++++++++ source/conf.py | 1 + 2 files changed, 10 insertions(+) create mode 100644 source/_static/styles.css diff --git a/source/_static/styles.css b/source/_static/styles.css new file mode 100644 index 0000000..be2364d --- /dev/null +++ b/source/_static/styles.css @@ -0,0 +1,9 @@ +/** + * Theme overrides - alabaster + */ +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} diff --git a/source/conf.py b/source/conf.py index 5799948..4d9e5f0 100644 --- a/source/conf.py +++ b/source/conf.py @@ -25,6 +25,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_static_path = ["_static"] +html_css_files = ["styles.css"] # -- Theme configuration ----------------------------------------------------- # https://alabaster.readthedocs.io/en/latest/customization.html From b6a0d1dc484479325475d22fcd5aa86aca579371 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 15:25:18 -0700 Subject: [PATCH 12/14] Finish first draft of introduction --- source/intro.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/source/intro.md b/source/intro.md index cca6d0d..f04252b 100644 --- a/source/intro.md +++ b/source/intro.md @@ -1,4 +1,4 @@ -# Intro +# Overview The **py_webauthn** library exposes a small number of core methods from the `webauthn` module: @@ -16,15 +16,24 @@ Additional data structures are available on `webauthn.helpers.structs`. These da ## Assumptions -Generally, the library makes the following assumptions about how a Relying Party implementing this library will interface with a webpage that will handle calling the WebAuthn API: +The library makes the following assumptions about how a Relying Party that is incorporating this library into their project will interface with the WebAuthn API: -- JSON is the preferred data type for transmitting registration and authentication options from the server to the webpage to feed to `navigator.credentials.create()` and `navigator.credentials.get()` respectively. -- JSON is the preferred data type for transmitting WebAuthn responses from the browser to the server. -- Bytes are not directly transmittable in either direction as JSON, and so should be encoded to and decoded from [base64url to avoid introducing any more dependencies than those that are specified in the WebAuthn spec](https://www.w3.org/TR/webauthn-2/#sctn-dependencies). - - See the [`WebAuthnBaseModel` struct](https://github.com/duo-labs/py_webauthn/blob/master/webauthn/helpers/structs.py#L13) for more information on how this is achieved +- **JSON** is the preferred data type for transmitting WebAuthn API options from the **server** to the **browser**. +- **JSON** is the preferred data type for transmitting WebAuthn responses from the **browser** to the **server**. +- Bytes are not directly transmittable in either direction as JSON, and so should be encoded to and decoded from **base64url** to avoid introducing any more dependencies than those that [are specified in the WebAuthn spec](https://www.w3.org/TR/webauthn-2/#sctn-dependencies). -The examples mentioned below include uses of the `options_to_json()` helper (see above) to show how easily `bytes` values in registration and authentication options can be encoded to base64url for transmission to the front end. +## Front End Libraries -The examples also include demonstrations of how to pass JSON-ified responses, using base64url encoding for `ArrayBuffer` values, into `parse_registration_credential_json` and `parse_authentication_credential_json` to be automatically parsed by the methods in this library. An RP can pair this with corresponding custom front end logic, or one of several frontend-specific libraries (like [@simplewebauthn/browser](https://www.npmjs.com/package/@simplewebauthn/browser), for example) to handle encoding and decoding such values to and from JSON. +py_webauthn is concerned exclusively with the **server** side of supporting WebAuthn. This means that Relying Parties will need to orchestrate calls to WebAuthn in the **browser** in some other way. -Other arguments into this library's methods that are defined as `bytes` are intended to be values stored entirely on the server. Such values can more easily exist as `bytes` without needing potentially extraneous encoding and decoding into other formats. Any encoding or decoding of such values in the name of storing them between steps in a WebAuthn ceremony is left up to the RP to achieve in an implementation-specific manner. +Typically this means **manually writing front end JavaScript** to coordinate encoding and decoding certain `bytes` values to and from **base64url** before calling WebAuthn's `navigator.credentials.create()` and `navigator.credentials.get()`. + +Relying Parties may also consider **using an existing third-party library** that takes care of all this. + +### @simplewebauthn/browser + +A great third-party library option is the **@simplewebauthn/browser** library out of the SimpleWebAuthn project: + + + +The methods available in **@simplewebauthn/browser** can accept JSON output from this project without modification, and their return values can be passed as-is into the `credential` argument of this library's response verification methods. See the SimpleWebAuthn docs for more information. From 591d5c332bf2c69ac88cb306467d4c48757851f7 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 16:32:04 -0700 Subject: [PATCH 13/14] Write Registration --- source/registration.md | 110 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/source/registration.md b/source/registration.md index 941ddfa..aa91072 100644 --- a/source/registration.md +++ b/source/registration.md @@ -1,9 +1,111 @@ # Registration -Coming Soon +WebAuthn registration ceremonies can be broken up into two operations: -## Examples +1. Generate WebAuthn API options +2. Verify the WebAuthn response -Examples of the use of `generate_registration_options` and `verify_registration_response` are available here: +Typical use of WebAuthn requires that a user account be identified at the time of registration. This can mean: - +- The user has successfully logged in via username and password and is proceeding through a prompt to upgrade to using passkeys +- The user has just clicked a magic link to confirm their email address during new account creation +- A logged-in user is adding a passkey to go passwordless on their next login + +Regardless of the scenario, the Relying Party should ensure they have **a strong sense of which user is logged in** before proceeding. + +## Generate Options + +Registration options are created using the following method: + +```py +from webauthn import generate_registration_options +from webauthn.helpers.structs import ( + AttestationConveyancePreference, + AuthenticatorAttachment, + AuthenticatorSelectionCriteria, + PublicKeyCredentialDescriptor, + ResidentKeyRequirement, +) +from webauthn.helpers.cose import COSEAlgorithmIdentifier + +# Simple Options +simple_registration_options = generate_registration_options( + rp_id="example.com", + rp_name="Example Co", + user_name="bob", +) + +# Complex Options +complex_registration_options = generate_registration_options( + rp_id="example.com", + rp_name="Example Co", + user_id=bytes([1, 2, 3, 4]), + user_name="Lee", + attestation=AttestationConveyancePreference.DIRECT, + authenticator_selection=AuthenticatorSelectionCriteria( + authenticator_attachment=AuthenticatorAttachment.PLATFORM, + resident_key=ResidentKeyRequirement.REQUIRED, + ), + challenge=bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + exclude_credentials=[ + PublicKeyCredentialDescriptor( + id=b"1234567890", + transports=[ + AuthenticatorTransport.INTERNAL, + AuthenticatorTransport.HYBRID, + ] + ), + ], + supported_pub_key_algs=[COSEAlgorithmIdentifier.ECDSA_SHA_512], + timeout=12000, +) +``` + +[See the docstrings](https://github.com/duo-labs/py_webauthn/blob/2219507f483e5592ec980ec95d97a5d3563fa45b/webauthn/registration/generate_registration_options.py#L42-L69) for details about the various required and optional **kwargs**. + +The output from `generate_registration_options()` can be passed into `webauthn.helpers.options_to_json()` to quickly convert them to a `str` value that can be sent to the browser as JSON. + +## Verify Response + +Registration responses can be verified using the following method: + +```py +from webauthn import ( + verify_registration_response, # <-- + base64url_to_bytes, +) + +verification = verify_registration_response( + # Can be a `str` or `dict` + credential={ + "id": "...", + "rawId": "...", + "response": { + "attestationObject": "...", + "clientDataJSON": "...", + "transports": ["internal"], + }, + "type": "public-key", + "clientExtensionResults": {}, + "authenticatorAttachment": "platform", + }, + # The value of `options.challenge` from above + expected_challenge=base64url_to_bytes("..."), + expected_origin="https://example.com", + expected_rp_id="example.com", + require_user_verification=True, +) +``` + +[See the docstrings](https://github.com/duo-labs/py_webauthn/blob/2219507f483e5592ec980ec95d97a5d3563fa45b/webauthn/registration/verify_registration_response.py#L67-L100) for details about the various required and optional **kwargs**. + +:::{note} +After verifying a response, store the following values from `verification` above for the logged-in user so that the user can use this passkey later to sign in: + +- `verification.credential_id` +- `verification.credential_public_key` +- `verification.sign_count` +- `verification.credential_device_type` +- `verification.credential_backed_up` +- The value of `response.transports` from the JSON passed in as the `credential` kwarg +::: From c3ca47f16d3cbc5c18ed8070a74dcea11acf6a3d Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 15 Aug 2024 16:32:10 -0700 Subject: [PATCH 14/14] Clarify authentication docs --- source/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/authentication.md b/source/authentication.md index 2b2e840..fd300de 100644 --- a/source/authentication.md +++ b/source/authentication.md @@ -4,6 +4,6 @@ Coming Soon ## Examples -Examples of the use of `generate_authentication_options` and `verify_authentication_response` are available here: +Additional examples of the use of `generate_authentication_options` and `verify_authentication_response` are available here: