use different webauthn for the browser

This commit is contained in:
Roberto Hidalgo 2023-01-04 13:44:20 -06:00
parent 9caab0ac94
commit c6af2c8a98
5 changed files with 45 additions and 16 deletions

View File

@ -234,7 +234,7 @@
</section> </section>
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/@teamhanko/hanko-webauthn@latest/dist/browser-global/hanko-webauthn.browser-global.js"></script> <script type="module" src="https://unpkg.com/@github/webauthn-json@2.0.2/dist/esm/webauthn-json.browser-ponyfill.js"></script>
<script type="module" src="/static/admin.js"></script> <script type="module" src="/static/admin.js"></script>
</body> </body>

View File

@ -22,7 +22,7 @@
<button id="rex">Abrir</button> <button id="rex">Abrir</button>
</form> </form>
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/@teamhanko/hanko-webauthn@latest/dist/browser-global/hanko-webauthn.browser-global.js"></script> <script type="module" src="https://unpkg.com/@github/webauthn-json@2.0.2/dist/esm/webauthn-json.browser-ponyfill.js"></script>
<script type="module" src="/static/index.js" async="async"></script> <script type="module" src="/static/index.js" async="async"></script>
</body> </body>
</html> </html>

View File

@ -38,6 +38,8 @@ type HTTPConfig struct {
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
// Origin describes the http origins to allow // Origin describes the http origins to allow
Origin string `yaml:"origin"` Origin string `yaml:"origin"`
// Protocol specifies the protocol for the webauthn origin
Protocol string `yaml:"protocol"`
} }
type Config struct { type Config struct {
@ -52,8 +54,9 @@ func ConfigDefaults(dbPath string) *Config {
return &Config{ return &Config{
DB: dbPath, DB: dbPath,
HTTP: &HTTPConfig{ HTTP: &HTTPConfig{
Listen: "localhost:8000", Listen: "localhost:8000",
Origin: "localhost", Origin: "localhost",
Protocol: "http",
}, },
} }
} }
@ -178,8 +181,9 @@ func Initialize(config *Config) (http.Handler, error) {
wan, err := webauthn.New(&webauthn.Config{ wan, err := webauthn.New(&webauthn.Config{
RPDisplayName: config.Name, RPDisplayName: config.Name,
RPID: config.HTTP.Origin, RPID: config.HTTP.Origin,
RPOrigins: []string{config.HTTP.Origin}, RPOrigins: []string{config.HTTP.Protocol + "://" + config.HTTP.Origin},
// RPIcon: "https://go-webauthn.local/logo.png", // For dev:
// RPOrigins: []string{config.HTTP.Protocol + "://" + config.HTTP.Listen},
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright © 2022 Roberto Hidalgo <nidito@un.rob.mx> // Copyright © 2022 Roberto Hidalgo <nidito@un.rob.mx>
const { create: createCredentials, get: getCredentials } = hankoWebAuthn; import * as webauthnJSON from 'https://unpkg.com/@github/webauthn-json@2.0.2/dist/esm/webauthn-json.browser-ponyfill.js'
const charsToEncode = /[\u007f-\uffff]/g; const charsToEncode = /[\u007f-\uffff]/g;
function JSONtob64(data) { function JSONtob64(data) {
return btoa(JSON.stringify(data).replace(charsToEncode, (c) => '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4))) return btoa(JSON.stringify(data).replace(charsToEncode, (c) => '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4)))
@ -21,7 +20,6 @@ export async function withAuth(target, config) {
return response return response
} }
console.log(Object.fromEntries(response.headers))
const challengeHeader = response.headers.get("webauthn") const challengeHeader = response.headers.get("webauthn")
if (!challengeHeader || challengeHeader == "") { if (!challengeHeader || challengeHeader == "") {
console.debug(`webauthn: success without auth`) console.debug(`webauthn: success without auth`)
@ -42,20 +40,41 @@ export async function withAuth(target, config) {
// we try to do that // we try to do that
await register(challenge, target) await register(challenge, target)
// and retry the original request if successful // and retry the original request if successful
return await withAuth(target, config) return await new Promise((res, rej) => {
setTimeout(async () => {
try {
res(await withAuth(target, config))
} catch(err) {
rej(err)
}
}, 1000)
})
} else if (step == "login") { } else if (step == "login") {
// server told us to use existing credential for request // server told us to use existing credential for request
return await login(challenge, target, config) return await login(challenge, target, config)
} }
throw `Unknown webauthn step: <${kind}>` throw `Unknown webauthn step: <${step}>`
} }
async function register(challenge) { async function register(challenge) {
console.info("webauthn: creating credentials") console.info("webauthn: initializing registration from challenge")
const credential = await createCredentials(challenge); console.dir(challenge)
const parsed = webauthnJSON.parseCreationOptionsFromJSON(challenge)
console.debug("webauthn: parsed challenge")
console.dir(parsed)
console.info("webauthn: issuing credential creation request to browser")
let credential
try {
credential = await webauthnJSON.create(parsed);
} catch (err) {
console.error("sigh", err)
throw err
}
console.debug(`webauthn: registering credentials with server: ${JSON.stringify(credential)}`) console.debug(`webauthn: registering credentials with server: ${JSON.stringify(credential)}`)
let response = await window.fetch("/api/webauthn/register", { let response = await window.fetch("/api/webauthn/register", {
credentials: "include", credentials: "include",
method: "POST", method: "POST",
@ -81,8 +100,14 @@ async function register(challenge) {
} }
async function login(challenge, target, config) { async function login(challenge, target, config) {
console.info("webauthn: fetching stored client credentials") console.info("webauthn: initializing login from challenge")
const credential = await getCredentials(challenge); console.dir(challenge)
const parsed = webauthnJSON.parseRequestOptionsFromJSON(challenge)
console.debug("webauthn: parsed challenge")
console.dir(parsed)
console.debug("webauthn: fetching stored client credentials")
let credential = await webauthnJSON.get(parsed);
config.credentials = "include" config.credentials = "include"
config.headers = config.headers || {} config.headers = config.headers || {}

View File

@ -40,7 +40,7 @@ type User struct {
} }
func (u *User) WebAuthnID() []byte { func (u *User) WebAuthnID() []byte {
return []byte(fmt.Sprintf("%d", u.ID)) return []byte(fmt.Sprintf("%d-%s", u.ID, u.Handle))
} }
// User Name according to the Relying Party // User Name according to the Relying Party