Skip to content

Commit

Permalink
feat: adding support for Passkeys (#99)
Browse files Browse the repository at this point in the history
* feat: adding support for Passkeys

* Update AuthProvider.tsx

* removing console logs
  • Loading branch information
aversini authored Jul 14, 2024
1 parent a421ddd commit d005ae1
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 25 deletions.
41 changes: 39 additions & 2 deletions examples/code-flow/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import { useState } from "react";

export const App = ({ timeout }: { timeout: string }) => {
const [accessToken, setAccessToken] = useState("");
const { login, logout, isAuthenticated, getAccessToken, isLoading } =
useAuth();
const {
login,
logout,
isAuthenticated,
getAccessToken,
isLoading,
registeringForPasskey,
loginWithPasskey,
} = useAuth();
const [apiResponse, setApiResponse] = useState({ data: "" });

console.info({ isAuthenticated, isLoading });
Expand Down Expand Up @@ -68,6 +75,20 @@ export const App = ({ timeout }: { timeout: string }) => {
setAccessToken(token);
};

const handleValidRegistration = async (e: {
preventDefault: () => void;
}) => {
e.preventDefault();
await registeringForPasskey();
};

const handleValidLoginWithPasskey = async (e: {
preventDefault: () => void;
}) => {
e.preventDefault();
await loginWithPasskey();
};

return (
<div className="prose prose-dark dark:prose-lighter">
<Header>
Expand All @@ -86,12 +107,28 @@ export const App = ({ timeout }: { timeout: string }) => {
Login (valid)
</Button>
<Button
spacing={{ r: 2 }}
size="small"
onClick={handleInvalidLogin}
disabled={isAuthenticated}
>
Login (invalid)
</Button>
<Button
spacing={{ r: 2 }}
size="small"
onClick={handleValidRegistration}
disabled={!isAuthenticated}
>
Register for Passkey (valid)
</Button>
<Button
size="small"
onClick={handleValidLoginWithPasskey}
disabled={isAuthenticated}
>
Login with Passkey (valid)
</Button>
</FlexgridItem>
<FlexgridItem>
<Button
Expand Down
1 change: 0 additions & 1 deletion examples/fastify-server/src/common/fastify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export const initServer = async () => {

fastify
.decorate("isAllowed", async (_request: any, _reply: any, done: any) => {
console.info(`==> [${Date.now()}] : hello?`);
try {
const accessToken = getToken(
_reply.request.headers,
Expand Down
1 change: 1 addition & 0 deletions packages/auth-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"react-dom": "18.3.1"
},
"dependencies": {
"@simplewebauthn/browser": "10.0.0",
"@versini/auth-common": "workspace:../auth-common",
"@versini/ui-hooks": "4.0.1",
"jose": "5.6.3",
Expand Down
5 changes: 4 additions & 1 deletion packages/auth-provider/src/common/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AUTH_TYPES } from "@versini/auth-common";
import {
ACTION_TYPE_LOADING,
ACTION_TYPE_LOGIN,
Expand Down Expand Up @@ -31,14 +32,16 @@ export type AuthState = {
export type LoginType = (
username: string,
password: string,
type?: string,
type?: typeof AUTH_TYPES.CODE | typeof AUTH_TYPES.PASSKEY,
) => Promise<boolean>;

export type AuthContextProps = {
login: LoginType;
logout: (e?: any) => void;
getAccessToken: () => Promise<string>;
getIdToken: () => string;
registeringForPasskey: () => Promise<any>;
loginWithPasskey: () => Promise<any>;
} & AuthState;

export type InternalActions =
Expand Down
139 changes: 139 additions & 0 deletions packages/auth-provider/src/common/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,142 @@ export const getAccessTokenSilently = async ({
};
}
};

const GRAPHQL_QUERIES = {
GET_REGISTRATION_OPTIONS: `mutation GetPasskeyRegistrationOptions(
$clientId: String!,
$username: String!,
$id: String!) {
getPasskeyRegistrationOptions(clientId: $clientId, username: $username, id: $id) {
challenge
rp {
id
name
}
user {
id
name
displayName
}
pubKeyCredParams {
type
alg
}
timeout
attestation
}
}`,
VERIFY_REGISTRATION: `mutation VerifyPasskeyRegistration(
$clientId: String!,
$username: String!,
$id: String!,
$registration: RegistrationOptionsInput!) {
verifyPasskeyRegistration(
clientId: $clientId,
username: $username,
id: $id,
registration: $registration) {
status
message
}
}`,
GET_AUTHENTICATION_OPTIONS: `mutation GetPasskeyAuthenticationOptions(
$id: String!,
$clientId: String!,
) {
getPasskeyAuthenticationOptions(
id: $id,
clientId: $clientId) {
rpId,
challenge,
allowCredentials,
timeout,
userVerification,
}
}`,
VERIFY_AUTHENTICATION: `mutation VerifyPasskeyAuthentication(
$clientId: String!,
$id: String!,
$authentication: AuthenticationOptionsInput!,
$nonce: String!,
$domain: String) {
verifyPasskeyAuthentication(
clientId: $clientId,
id: $id,
authentication: $authentication,
nonce: $nonce,
domain: $domain) {
status,
idToken,
accessToken,
refreshToken,
userId,
username,
}
}`,
};
export const SERVICE_TYPES = {
GET_REGISTRATION_OPTIONS: {
schema: GRAPHQL_QUERIES.GET_REGISTRATION_OPTIONS,
method: "getPasskeyRegistrationOptions",
},
VERIFY_REGISTRATION: {
schema: GRAPHQL_QUERIES.VERIFY_REGISTRATION,
method: "verifyPasskeyRegistration",
},
GET_AUTHENTICATION_OPTIONS: {
schema: GRAPHQL_QUERIES.GET_AUTHENTICATION_OPTIONS,
method: "getPasskeyAuthenticationOptions",
},
VERIFY_AUTHENTICATION: {
schema: GRAPHQL_QUERIES.VERIFY_AUTHENTICATION,
method: "verifyPasskeyAuthentication",
},
};

export const graphQLCall = async ({
accessToken,
type,
clientId,
params = {},
}: {
accessToken: string;
clientId: string;
type: any;
params?: any;
}) => {
try {
const requestData = type?.data ? type.data(params) : params;
const authorization = `Bearer ${accessToken}`;
const response = await fetch(
isDev ? `${API_ENDPOINT.dev}/graphql` : `${API_ENDPOINT.prod}/graphql`,
{
method: "POST",
credentials: "include",
headers: {
authorization,
"Content-Type": "application/json",
Accept: "application/json",
[HEADERS.CLIENT_ID]: `${clientId}`,
},
body: JSON.stringify({
query: type.schema,
variables: requestData,
}),
},
);
if (response.status !== 200) {
return { status: response.status, data: [] };
}
const { data, errors } = await response.json();
return {
status: response.status,
data: data[type.method],
errors,
};
} catch (_error) {
console.error(_error);
return { status: 500, data: [] };
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export const AuthContext = createContext<AuthContextProps>({
logout: stub,
getAccessToken: stub,
getIdToken: stub,
registeringForPasskey: stub,
loginWithPasskey: stub,
logoutReason: "",
});
Loading

0 comments on commit d005ae1

Please sign in to comment.