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

Authentication with security keys and the WebAuthn API #910

Merged
merged 26 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1c3c7c2
feat: wip webauthn
plmercereau Aug 30, 2022
06bc885
refactor: simplify
plmercereau Aug 30, 2022
9bf938a
refactor: rename to passwordlessWebauthn
plmercereau Sep 1, 2022
9687581
feat: add device nickname and adapt example
plmercereau Sep 1, 2022
f42b693
feat: add webauthn to nhost.auth.signIn
plmercereau Sep 1, 2022
0feac15
fix: bundle @simplewebauth into @nhost/core
plmercereau Sep 2, 2022
0e11267
refactor: explicitly bundle simple webtauthn
plmercereau Sep 2, 2022
b9c012e
test: adapt navigation
plmercereau Sep 2, 2022
f6cfca9
refactor: change to "security key" terminology
plmercereau Sep 2, 2022
4629b95
refactor: change to "security key" terminology
plmercereau Sep 2, 2022
2f432b5
feat: useSecurityKeys
plmercereau Sep 3, 2022
4e7d1fb
docs: inline documentation
plmercereau Sep 5, 2022
bf4cc38
refactor: remove rp_id
plmercereau Sep 5, 2022
9e57a2b
Update packages/react/src/useSecurityKeys.ts
plmercereau Sep 6, 2022
13937fa
Update packages/react/src/useSecurityKeys.ts
plmercereau Sep 6, 2022
8e03774
refactor: types
plmercereau Sep 6, 2022
3d7369a
refactor: break blocks with `return` statements
plmercereau Sep 6, 2022
3a217fd
style: use #graphql
plmercereau Sep 6, 2022
618e5a8
chore: merge main
plmercereau Sep 6, 2022
26cb0de
test: webauthn
plmercereau Sep 7, 2022
5fc3b64
Merge branch 'bump-hasura-auth-0.11' into webauthn
plmercereau Sep 8, 2022
90843a9
refactor: early returns
plmercereau Sep 9, 2022
07eb26c
refactor: improve typings
plmercereau Sep 9, 2022
ad0d564
refactor: don't list or remove security keys
plmercereau Sep 12, 2022
164e8ca
doc: inline
plmercereau Sep 12, 2022
d7b5261
Merge branch 'main' into webauthn
plmercereau Sep 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
title: useAddSecurityKey()
sidebar_label: useAddSecurityKey()
slug: /reference/nextjs/use-add-security-key
description: Use the hook `useAddSecurityKey` to add a WebAuthn security key.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useAddSecurityKey.ts#L45
---

# `useAddSecurityKey()`

Use the hook `useAddSecurityKey` to add a WebAuthn security key.

```tsx
const { add, isLoading, isSuccess, isError, error } = useSecurityKeys()

const handleFormSubmit = async (e) => {
e.preventDefault()

await add('key nickname')
}
```
36 changes: 0 additions & 36 deletions docs/docs/reference/docgen/nextjs/content/use-security-keys.mdx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
title: SecurityKeysHookResult
sidebar_label: SecurityKeysHookResult
title: AddSecuritKeyHookResult
sidebar_label: AddSecuritKeyHookResult
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSecurityKeys.ts#L29
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useAddSecurityKey.ts#L20
---

# `SecurityKeysHookResult`
# `AddSecuritKeyHookResult`

## Parameters

Expand Down Expand Up @@ -42,15 +42,3 @@ Returns `true` if the action is successful.
Add a security key to the current user with the WebAuthn API

---

**<span className="parameter-name">list</span>** <span className="optional-status">required</span> `Array<SecurityKey>`

List the security keys of the current user

---

**<span className="parameter-name">remove</span>** <span className="optional-status">required</span> `RemoveSecurityKeyHandler`

Remove the given security key from the list of allowed keys for the current user

---
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
title: useAddSecurityKey()
sidebar_label: useAddSecurityKey()
slug: /reference/react/use-add-security-key
description: Use the hook `useAddSecurityKey` to add a WebAuthn security key.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useAddSecurityKey.ts#L45
---

# `useAddSecurityKey()`

Use the hook `useAddSecurityKey` to add a WebAuthn security key.

```tsx
const { add, isLoading, isSuccess, isError, error } = useSecurityKeys()

const handleFormSubmit = async (e) => {
e.preventDefault()

await add('key nickname')
}
```
36 changes: 0 additions & 36 deletions docs/docs/reference/docgen/react/content/use-security-keys.mdx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
title: SecurityKeysHookResult
sidebar_label: SecurityKeysHookResult
title: AddSecuritKeyHookResult
sidebar_label: AddSecuritKeyHookResult
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSecurityKeys.ts#L29
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useAddSecurityKey.ts#L20
---

# `SecurityKeysHookResult`
# `AddSecuritKeyHookResult`

## Parameters

Expand Down Expand Up @@ -42,15 +42,3 @@ Returns `true` if the action is successful.
Add a security key to the current user with the WebAuthn API

---

**<span className="parameter-name">list</span>** <span className="optional-status">required</span> `Array<SecurityKey>`

List the security keys of the current user

---

**<span className="parameter-name">remove</span>** <span className="optional-status">required</span> `RemoveSecurityKeyHandler`

Remove the given security key from the list of allowed keys for the current user

---
10 changes: 6 additions & 4 deletions examples/react-apollo/src/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1215,14 +1215,16 @@ export type AddItemMutation = { __typename?: 'mutation_root', insertTodo?: { __t

export type NewTodoFragment = { __typename?: 'todos', id: string, contents: string };

export type MyAuthenticatorsQueryVariables = Exact<{ [key: string]: never; }>;
export type SecurityKeysQueryVariables = Exact<{
userId: Scalars['uuid'];
}>;


export type MyAuthenticatorsQuery = { __typename?: 'query_root', authUserAuthenticators: Array<{ __typename?: 'authUserAuthenticators', id: string, nickname?: string | null }> };
export type SecurityKeysQuery = { __typename?: 'query_root', authUserAuthenticators: Array<{ __typename?: 'authUserAuthenticators', id: string, nickname?: string | null }> };

export type RemoveAuthenticatorMutationVariables = Exact<{
export type RemoveSecurityKeyMutationVariables = Exact<{
id: Scalars['uuid'];
}>;


export type RemoveAuthenticatorMutation = { __typename?: 'mutation_root', deleteAuthUserAuthenticator?: { __typename?: 'authUserAuthenticators', id: string } | null };
export type RemoveSecurityKeyMutation = { __typename?: 'mutation_root', deleteAuthUserAuthenticator?: { __typename?: 'authUserAuthenticators', id: string } | null };
70 changes: 56 additions & 14 deletions examples/react-apollo/src/profile/security-keys.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,64 @@
import { useState } from 'react'
import { FaMinus } from 'react-icons/fa'
import { RemoveSecurityKeyMutation, SecurityKeysQuery } from 'src/generated'

import { gql, useMutation } from '@apollo/client'
import { ActionIcon, Button, Card, SimpleGrid, Table, TextInput, Title } from '@mantine/core'
import { useInputState } from '@mantine/hooks'
import { useSecurityKeys } from '@nhost/react'
import { useAddSecurityKey, useUserId } from '@nhost/react'
import { useAuthQuery } from '@nhost/react-apollo'

const SECURITY_KEYS_LIST = gql`
query securityKeys($userId: uuid!) {
authUserAuthenticators(where: { userId: { _eq: $userId } }) {
id
nickname
}
}
`

const REMOVE_SECURITY_KEY = gql`
mutation removeSecurityKey($id: uuid!) {
deleteAuthUserAuthenticator(id: $id) {
id
}
}
`

export const SecurityKeys: React.FC = () => {
const { list, add, remove } = useSecurityKeys()
const { add } = useAddSecurityKey()
const userId = useUserId()
const [nickname, setNickname] = useInputState('')
const [list, setList] = useState<{ id: string; nickname?: string | null }[]>([])
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId },
onCompleted: ({ authUserAuthenticators }) => {
if (authUserAuthenticators) {
setList(authUserAuthenticators || [])
}
}
})

const addKey = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const { key, error } = await add(nickname)
if (error) {
console.log(error)
} else {
setNickname('')
}
if (key) {
setList([...list, key])
}
elitan marked this conversation as resolved.
Show resolved Hide resolved
}
const [removeKey] = useMutation<RemoveSecurityKeyMutation>(REMOVE_SECURITY_KEY, {
onCompleted: ({ deleteAuthUserAuthenticator }) => {
if (deleteAuthUserAuthenticator?.id) {
setList(list.filter((item) => item.id !== deleteAuthUserAuthenticator.id))
}
}
})

return (
<Card shadow="sm" p="lg" m="sm">
<Title>Security keys</Title>
Expand All @@ -20,25 +72,15 @@ export const SecurityKeys: React.FC = () => {
<tr key={id}>
<td>{nickname || id}</td>
<td>
<ActionIcon onClick={() => remove(id)} color="red">
<ActionIcon onClick={() => removeKey({ variables: { id } })} color="red">
<FaMinus />
</ActionIcon>
</td>
</tr>
))}
</tbody>
</Table>
<form
onSubmit={async (e) => {
e.preventDefault()
const { error } = await add(nickname)
if (error) {
console.log(error)
} else {
setNickname('')
}
}}
>
<form onSubmit={addKey}>
<SimpleGrid cols={2}>
<TextInput
autoFocus
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './client'
export * from './provider'
export * from './useAccessToken'
export * from './useAddSecurityKey'
export * from './useAuthenticated'
export * from './useAuthenticationStatus'
export * from './useAuthInterpreter'
Expand All @@ -18,7 +19,6 @@ export * from './useNhostBackendUrl'
export * from './useNhostClient'
export * from './useProviderLink'
export * from './useResetPassword'
export * from './useSecurityKeys'
export * from './useSendVerificationEmail'
export * from './useSignInAnonymous'
export * from './useSignInEmailPassword'
Expand Down
65 changes: 65 additions & 0 deletions packages/react/src/useAddSecurityKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState } from 'react'

import {
ActionErrorState,
ActionSuccessState,
AddSecurityKeyHandlerResult,
addSecurityKeyPromise,
ErrorPayload
} from '@nhost/core'

import { useNhostClient } from './useNhostClient'

interface AddSecurityKeyHandler {
(
/** Optional human-readable name of the security key */
nickname?: string
): Promise<AddSecurityKeyHandlerResult>
}

export interface AddSecuritKeyHookResult extends ActionErrorState, ActionSuccessState {
/** Add a security key to the current user with the WebAuthn API */
add: AddSecurityKeyHandler
}

interface AddSecuritKeyHook {
(): AddSecuritKeyHookResult
}

/**
* Use the hook `useAddSecurityKey` to add a WebAuthn security key.
*
* @example
* ```tsx
* const { add, isLoading, isSuccess, isError, error } = useSecurityKeys()
plmercereau marked this conversation as resolved.
Show resolved Hide resolved
*
* const handleFormSubmit = async (e) => {
* e.preventDefault();
*
* await add('key nickname')
* }
* ```
*
* @docs https://docs.nhost.io/reference/react/use-add-security-key
*/
export const useAddSecurityKey: AddSecuritKeyHook = () => {
const nhost = useNhostClient()
const [error, setError] = useState<ErrorPayload | null>(null)
const isSuccess = !error
const isError = !!error

const [isLoading, setIsLoading] = useState(false)

const add: AddSecurityKeyHandler = async (nickname) => {
setIsLoading(true)
const result = await addSecurityKeyPromise(nhost.auth.client, nickname)
const { error } = result
if (error) {
setError(error)
}
setIsLoading(false)
return result
}

return { add, isLoading, isSuccess, isError, error }
}