Skip to content

Commit

Permalink
Passkey edge cases (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
hwhmeikle authored Oct 25, 2024
1 parent a479b3b commit 756ece7
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 3 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authsignal/react",
"version": "0.2.0",
"version": "0.2.1",
"description": "",
"keywords": [
"authsignal",
Expand Down Expand Up @@ -33,6 +33,7 @@
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@simplewebauthn/browser": "^11.0.0",
"clsx": "^2.1.1",
"input-otp": "^1.2.4",
"react-hook-form": "^7.53.0",
Expand Down
35 changes: 34 additions & 1 deletion src/components/challenge/screens/passkey-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React, { useCallback } from "react";
import { Drawer } from "vaul";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";
import { browserSupportsWebAuthn } from "@simplewebauthn/browser";
import { isIframeInSafari } from "../../../lib/device";

type PasskeyChallengeProps = {
token: string; // TODO: This should be set in the web sdk
Expand All @@ -11,15 +13,23 @@ type PasskeyChallengeProps = {
enum State {
AUTHENTICATING = "AUTHENTICATING",
ERROR = "ERROR",
NOT_SUPPORTED = "NOT_SUPPORTED",
}

export function PasskeyChallenge({ token }: PasskeyChallengeProps) {
const [state, setState] = React.useState<State>(State.AUTHENTICATING);
const [state, setState] = React.useState<State | undefined>(
isIframeInSafari() ? undefined : State.AUTHENTICATING,
);

const { handleChallengeSuccess, authsignal, isDesktop } =
useChallengeContext();

const handlePasskeyAuthentication = useCallback(async () => {
if (!browserSupportsWebAuthn()) {
setState(State.NOT_SUPPORTED);
return;
}

const handleError = () => {
setState(State.ERROR);
};
Expand Down Expand Up @@ -54,6 +64,29 @@ export function PasskeyChallenge({ token }: PasskeyChallengeProps) {

return (
<div className="as-space-y-6">
{!state && isIframeInSafari() && (
<>
<button
className="as-inline-flex as-w-full as-items-center as-justify-center as-rounded-lg as-bg-primary as-px-3 as-py-2 as-text-sm as-font-medium as-text-primary-foreground"
type="button"
onClick={handlePasskeyAuthentication}
>
Authenticate with passkey
</button>
</>
)}

{state === State.NOT_SUPPORTED && (
<div className="as-space-y-2">
<TitleComponent className="as-text-xl as-font-medium as-text-foreground">
Passkeys not supported
</TitleComponent>
<p className="as-text-sm as-text-foreground">
Your browser does not support passkeys.
</p>
</div>
)}

{state === State.AUTHENTICATING && (
<>
<div className="as-space-y-2">
Expand Down
34 changes: 33 additions & 1 deletion src/components/challenge/screens/security-key-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@ import React, { useCallback } from "react";
import { Drawer } from "vaul";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";
import { browserSupportsWebAuthn } from "@simplewebauthn/browser";
import { isIframeInSafari } from "../../../lib/device";

enum State {
AUTHENTICATING = "AUTHENTICATING",
ERROR = "ERROR",
NOT_SUPPORTED = "NOT_SUPPORTED",
}

export function SecurityKeyChallenge() {
const [state, setState] = React.useState<State>(State.AUTHENTICATING);
const [state, setState] = React.useState<State | undefined>(
isIframeInSafari() ? undefined : State.AUTHENTICATING,
);

const { handleChallengeSuccess, authsignal, isDesktop } =
useChallengeContext();

const handleSecurityKeyAuthentication = useCallback(async () => {
if (!browserSupportsWebAuthn()) {
setState(State.NOT_SUPPORTED);
return;
}
const handleError = () => {
setState(State.ERROR);
};
Expand Down Expand Up @@ -48,6 +57,29 @@ export function SecurityKeyChallenge() {

return (
<div className="as-space-y-6">
{!state && isIframeInSafari() && (
<>
<button
className="as-inline-flex as-w-full as-items-center as-justify-center as-rounded-lg as-bg-primary as-px-3 as-py-2 as-text-sm as-font-medium as-text-primary-foreground"
type="button"
onClick={handleSecurityKeyAuthentication}
>
Authenticate with security key
</button>
</>
)}

{state === State.NOT_SUPPORTED && (
<div className="as-space-y-2">
<TitleComponent className="as-text-xl as-font-medium as-text-foreground">
Security keys not supported
</TitleComponent>
<p className="as-text-sm as-text-foreground">
Your browser does not support security keys.
</p>
</div>
)}

{state === State.AUTHENTICATING && (
<>
<div className="as-space-y-2">
Expand Down
25 changes: 25 additions & 0 deletions src/lib/device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Inspired by https://github.com/adobe/react-spectrum/blob/93c26d8bd2dfe48a815f08c58925a977b94d6fdd/packages/%40react-aria/utils/src/platform.ts

function testUserAgent(re: RegExp) {
if (typeof window === "undefined" || window.navigator == null) {
return false;
}
return (
// @ts-expect-error userAgentData is not supported in all browsers
window.navigator["userAgentData"]?.brands.some(
(brand: { brand: string; version: string }) => re.test(brand.brand),
) || re.test(window.navigator.userAgent)
);
}

function isChrome() {
return testUserAgent(/Chrome/i);
}

function isWebKit() {
return testUserAgent(/AppleWebKit/i) && !isChrome();
}

export function isIframeInSafari() {
return window.self !== window.top && isWebKit();
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -766,11 +766,23 @@
dependencies:
"@simplewebauthn/types" "^10.0.0"

"@simplewebauthn/browser@^11.0.0":
version "11.0.0"
resolved "https://registry.yarnpkg.com/@simplewebauthn/browser/-/browser-11.0.0.tgz#270b47039b4561199bc16f25197431c8f10dbfb5"
integrity sha512-KEGCStrl08QC2I561BzxqGiwoknblP6O1YW7jApdXLPtIqZ+vgJYAv8ssLCdm1wD8HGAHd49CJLkUF8X70x/pg==
dependencies:
"@simplewebauthn/types" "^11.0.0"

"@simplewebauthn/types@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@simplewebauthn/types/-/types-10.0.0.tgz#a07259d42fbdff7a014473f78401d262b298ed8e"
integrity sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==

"@simplewebauthn/types@^11.0.0":
version "11.0.0"
resolved "https://registry.yarnpkg.com/@simplewebauthn/types/-/types-11.0.0.tgz#f329abc4c1bc3b18d6c7a4346af9fdec90d8a67e"
integrity sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==

"@trysound/[email protected]":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
Expand Down

0 comments on commit 756ece7

Please sign in to comment.