mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-15 19:33:44 +00:00
passkey u2f improvements
This commit is contained in:
@@ -141,11 +141,12 @@ Requests to the APIs made:
|
|||||||
- `updateSession()`
|
- `updateSession()`
|
||||||
|
|
||||||
When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.DISCOURAGED` as this will request the webAuthN method as second factor and not as primary method.
|
When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.DISCOURAGED` as this will request the webAuthN method as second factor and not as primary method.
|
||||||
After updating the session, the user is signed in.
|
After updating the session, the user is **always** signed in. :warning: required as this page is a follow up for setting up a u2f method.
|
||||||
|
|
||||||
### /passkey
|
### /passkey
|
||||||
|
|
||||||
This page requests a webAuthN challenge for the user and updates the session afterwards.
|
This page requests a webAuthN challenge for the user and updates the session afterwards.
|
||||||
|
It is invoked directly after setting up a passkey `/passkey/set` or when loggin in a user after `/loginname`.
|
||||||
|
|
||||||
<img src="./screenshots/passkey.png" alt="/passkey" width="400px" />
|
<img src="./screenshots/passkey.png" alt="/passkey" width="400px" />
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ Requests to the APIs made:
|
|||||||
- `updateSession()`
|
- `updateSession()`
|
||||||
|
|
||||||
When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login.
|
When updating the session for the webAuthN challenge, we set `userVerificationRequirement` to `UserVerificationRequirement.REQUIRED` as this will request the webAuthN method as primary method to login.
|
||||||
After updating the session, the user is signed in.
|
After updating the session, the user is **always** signed in. :warning: required as this page is a follow up for setting up a passkey
|
||||||
|
|
||||||
> NOTE: This page currently does not check whether a user contains passkeys. If this method is not available, this page should not be used.
|
> NOTE: This page currently does not check whether a user contains passkeys. If this method is not available, this page should not be used.
|
||||||
|
|
||||||
@@ -185,19 +186,20 @@ At the moment, U2F methods are hidden if a method is already added on the users
|
|||||||
|
|
||||||
### /passkey/set
|
### /passkey/set
|
||||||
|
|
||||||
<img src="./screenshots/passkeyset.png" alt="/passkey/set" width="400px" />
|
|
||||||
|
|
||||||
This page sets a passkey method for a user. This page can be either enforced, or optional depending on the Login Settings.
|
This page sets a passkey method for a user. This page can be either enforced, or optional depending on the Login Settings.
|
||||||
|
|
||||||
|
<img src="./screenshots/passkeyset.png" alt="/passkey/set" width="400px" />
|
||||||
|
|
||||||
Requests to the APIs made:
|
Requests to the APIs made:
|
||||||
|
|
||||||
- `getBrandingSettings(org?)`
|
- `getBrandingSettings(org?)`
|
||||||
- `getSession()`
|
- `getSession()`
|
||||||
- `registerPasskeyLink()`
|
- `createPasskeyRegistrationLink(token)` :warning: This request requires the session token
|
||||||
|
- `registerPasskey()`
|
||||||
- `verifyPasskey()`
|
- `verifyPasskey()`
|
||||||
|
|
||||||
If the loginname decides to redirect the user to this page, a button to skip appears which will sign the user in afterwards.
|
If the loginname decides to redirect the user to this page, a button to skip appears which will sign the user in afterwards.
|
||||||
If a passkey is registered, we redirect the user to `/passkey` to again verify it and sign in with the new method.
|
After a passkey is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow.
|
||||||
|
|
||||||
> NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611)
|
> NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611)
|
||||||
|
|
||||||
@@ -205,8 +207,45 @@ If a passkey is registered, we redirect the user to `/passkey` to again verify i
|
|||||||
|
|
||||||
### /u2f/set
|
### /u2f/set
|
||||||
|
|
||||||
|
This page registers a U2F method for a user.
|
||||||
|
|
||||||
|
<img src="./screenshots/u2fset.png" alt="/u2f/set" width="400px" />
|
||||||
|
|
||||||
|
Requests to the APIs made:
|
||||||
|
|
||||||
|
- `getBrandingSettings(org?)`
|
||||||
|
- `getSession()`
|
||||||
|
- `registerU2F(token)` :warning: This request requires the session token
|
||||||
|
- `verifyU2FRegistration()`
|
||||||
|
|
||||||
|
After a u2f method is registered, we redirect the user to `/passkey` to verify it again and sign in with the new method. The `createPasskeyRegistrationLink()` uses the token of the session which is determined by the flow.
|
||||||
|
|
||||||
|
> NOTE: Redirecting the user to `/passkey` will not be required in future and the currently used session will be hydrated directly after registering. (https://github.com/zitadel/zitadel/issues/8611)
|
||||||
|
|
||||||
### /register
|
### /register
|
||||||
|
|
||||||
|
This page shows a register page, which gets firstname and lastname of a user as well as the email. It offers to setup a user, using password or passkeys.
|
||||||
|
|
||||||
|
<img src="./screenshots/register.png" alt="/register" width="400px" />
|
||||||
|
|
||||||
|
Requests to the APIs made:
|
||||||
|
|
||||||
|
- `listOrganizations()` :warning: TODO: determine the default organization if no context is set
|
||||||
|
- `getLegalAndSupportSettings(org)`
|
||||||
|
- `getPasswordComplexitySettings()`
|
||||||
|
- `getBrandingSettings()`
|
||||||
|
- `addHumanUser()`
|
||||||
|
- `createSession()`
|
||||||
|
- `getSession()`
|
||||||
|
|
||||||
|
To register a user, the organization where the resource will be created is determined first. If no context is provided via url, we fall back to the default organization of the instance.
|
||||||
|
|
||||||
|
**PASSWORD:** If a password is set, the user is created as a resource, then a session using the password check is created immediately.
|
||||||
|
|
||||||
|
**PASSKEY:** If passkey is selected, the user is created as a resource first, then a session using the userId is created immediately. This session does not yet contain a check, we therefore redirect the user to setup a passkey at `/passkey/set`. As the passkey set page verifies the passkey right afterwards, the process ends with a signed in user.
|
||||||
|
|
||||||
|
> NOTE: https://github.com/zitadel/zitadel/issues/8616 to determine the default organization of an instance must be implemented in order to correctly use the legal-, login-, branding- and complexitysettings.
|
||||||
|
|
||||||
### /idp
|
### /idp
|
||||||
|
|
||||||
This page doubles as /loginname but limits it to choose from IDPs
|
This page doubles as /loginname but limits it to choose from IDPs
|
||||||
|
|||||||
BIN
apps/login/screenshots/register.png
Normal file
BIN
apps/login/screenshots/register.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
@@ -49,7 +49,12 @@ export async function registerPasskeyLink(
|
|||||||
throw new Error("Could not get session");
|
throw new Error("Could not get session");
|
||||||
}
|
}
|
||||||
// TODO: add org context
|
// TODO: add org context
|
||||||
const registerLink = await createPasskeyRegistrationLink(userId);
|
|
||||||
|
// use session token to add the passkey
|
||||||
|
const registerLink = await createPasskeyRegistrationLink(
|
||||||
|
userId,
|
||||||
|
sessionCookie.token,
|
||||||
|
);
|
||||||
|
|
||||||
if (!registerLink.code) {
|
if (!registerLink.code) {
|
||||||
throw new Error("Missing code in response");
|
throw new Error("Missing code in response");
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function addU2F(command: RegisterU2FCommand) {
|
|||||||
return { error: "Could not get session" };
|
return { error: "Could not get session" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return registerU2F(userId, domain);
|
return registerU2F(userId, domain, sessionCookie.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyU2F(command: VerifyU2FCommand) {
|
export async function verifyU2F(command: VerifyU2FCommand) {
|
||||||
|
|||||||
@@ -442,7 +442,6 @@ export function createUser(
|
|||||||
info: IDPInformation,
|
info: IDPInformation,
|
||||||
) {
|
) {
|
||||||
const userData = PROVIDER_MAPPING[provider](info);
|
const userData = PROVIDER_MAPPING[provider](info);
|
||||||
console.log("ud", userData);
|
|
||||||
return userService.addHumanUser(userData, {});
|
return userService.addHumanUser(userData, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,23 +467,15 @@ export async function passwordReset(userId: string) {
|
|||||||
*/
|
*/
|
||||||
export async function createPasskeyRegistrationLink(
|
export async function createPasskeyRegistrationLink(
|
||||||
userId: string,
|
userId: string,
|
||||||
token?: string,
|
token: string,
|
||||||
) {
|
) {
|
||||||
// let userService;
|
const transport = createServerTransport(token, {
|
||||||
// if (token) {
|
baseUrl: process.env.ZITADEL_API_URL!,
|
||||||
// const authConfig: ZitadelServerOptions = {
|
httpVersion: "2",
|
||||||
// name: "zitadel login",
|
});
|
||||||
// apiUrl: process.env.ZITADEL_API_URL ?? "",
|
|
||||||
// token: token,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const sessionUser = initializeServer(authConfig);
|
|
||||||
// userService = user.getUser(sessionUser);
|
|
||||||
// } else {
|
|
||||||
// userService = user.getUser(server);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return userService.createPasskeyRegistrationLink({
|
const service = createUserServiceClient(transport);
|
||||||
|
return service.createPasskeyRegistrationLink({
|
||||||
userId,
|
userId,
|
||||||
medium: {
|
medium: {
|
||||||
case: "returnCode",
|
case: "returnCode",
|
||||||
@@ -499,8 +490,18 @@ export async function createPasskeyRegistrationLink(
|
|||||||
* @param domain the domain on which the factor is registered
|
* @param domain the domain on which the factor is registered
|
||||||
* @returns the newly set email
|
* @returns the newly set email
|
||||||
*/
|
*/
|
||||||
export async function registerU2F(userId: string, domain: string) {
|
export async function registerU2F(
|
||||||
return userService.registerU2F({
|
userId: string,
|
||||||
|
domain: string,
|
||||||
|
token: string,
|
||||||
|
) {
|
||||||
|
const transport = createServerTransport(token, {
|
||||||
|
baseUrl: process.env.ZITADEL_API_URL!,
|
||||||
|
httpVersion: "2",
|
||||||
|
});
|
||||||
|
|
||||||
|
const service = createUserServiceClient(transport);
|
||||||
|
return service.registerU2F({
|
||||||
userId,
|
userId,
|
||||||
domain,
|
domain,
|
||||||
});
|
});
|
||||||
@@ -550,7 +551,6 @@ export async function registerPasskey(
|
|||||||
userId,
|
userId,
|
||||||
code,
|
code,
|
||||||
domain,
|
domain,
|
||||||
// authenticator:
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ export default function RegisterPasskey({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continueAndLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
function continueAndLogin() {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
@@ -147,41 +151,11 @@ export default function RegisterPasskey({
|
|||||||
|
|
||||||
if (authRequestId) {
|
if (authRequestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("authRequestId", authRequestId);
|
||||||
params.set("sessionId", sessionId);
|
|
||||||
|
|
||||||
router.push("/passkey?" + params);
|
|
||||||
} else {
|
|
||||||
continueAndLogin();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function continueAndLogin() {
|
params.set("sessionId", sessionId);
|
||||||
if (authRequestId) {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
authRequest: authRequestId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sessionId) {
|
router.push("/passkey?" + params);
|
||||||
params.set("sessionId", sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (organization) {
|
|
||||||
params.set("organization", organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push("/login?" + params);
|
|
||||||
} else {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
if (sessionId) {
|
|
||||||
params.append("sessionId", sessionId);
|
|
||||||
}
|
|
||||||
if (organization) {
|
|
||||||
params.append("organization", organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push("/signedin?" + params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user