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

feat: WebAuthN Sign In, Sign Up and Options methods support - NEW #952

Merged
merged 26 commits into from
Nov 25, 2024

Conversation

victorbojica
Copy link

@victorbojica victorbojica commented Oct 17, 2024

ONLY REVIEW REQUIRED. NOTHING TESTED YET.

Summary of change

Implement WebAuthN support according to:
https://docs.google.com/document/d/1G7tO9_dSNi8wur3ajGg4pq-wiHatKDbHv2sBt-uSbQg/edit#heading=h.olee876uqu8a

Related

Check #942 for more feedback

Test Plan

No testing at the moment.

Documentation changes

Will have to add WebAuthN recipe documentation

Checklist for important updates

  • Changelog has been updated
  • coreDriverInterfaceSupported.json file has been updated (if needed)
    • Along with the associated array in lib/ts/version.ts
  • frontendDriverInterfaceSupported.json file has been updated (if needed)
  • Changes to the version if needed
    • In package.json
    • In package-lock.json
    • In lib/ts/version.ts
  • Had run npm run build-pretty
  • Had installed and ran the pre-commit hook
  • If new thirdparty provider is added,
    • update switch statement in recipe/thirdparty/providers/configUtils.ts file, createProvider function.
    • add an icon on the user management dashboard.
  • Issue this PR against the latest non released version branch.
    • To know which one it is, run find the latest released tag (git tag) in the format vX.Y.Z, and then find the latest branch (git branch --all) whose X.Y is greater than the latest released tag.
    • If no such branch exists, then create one from the latest released branch.
  • If have added a new web framework, update the add-ts-no-check.js file to include that
  • If added a new recipe / api interface, then make sure that the implementation of it uses NON arrow functions only (like someFunc: function () {..}).
  • If added a new recipe, then make sure to expose it inside the recipe folder present in the root of this repo. We also need to expose its types.
  • If added a new entry point, then make sure that it is importable by adding it to the exports in package.json

Remaining TODOs for this PR

  • Check support for other authenticators
  • Account recovery flow
  • Options testing
  • Sign In testing
  • Sign Up testing
  • Multiple authenticators testing
  • Do we need verifyCredentials in recipeInterface?

@victorbojica victorbojica changed the title Feat/webauthn/basic methods feat: WebAuthN Sign In, Sign Up and Options methods support - NEW Oct 17, 2024
lib/ts/recipe/multifactorauth/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/multifactorauth/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
Copy link

cloudflare-workers-and-pages bot commented Oct 18, 2024

Deploying supertokens-node-pr-check-for-edge-function-compat with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce371d1
Status: ✅  Deploy successful!
Preview URL: https://fa49a36e.supertokens-node-b95.pages.dev

View logs

lib/ts/recipe/multifactorauth/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/multitenancy/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/multifactorauth/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
options: APIOptions;
userContext: UserContext;
} & ({ email: string } | { recoverAccountToken: string })
) => Promise<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does webauthn flow work for native mobile apps?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, through WebViews starting from iOS 16 and Android 9 (and 14 in some cases): https://passkeys.dev/docs/reference/android/ and https://passkeys.dev/docs/reference/ios/.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Show resolved Hide resolved
lib/ts/recipe/webauthn/types.ts Outdated Show resolved Hide resolved
Comment on lines 490 to 494
relyingPartyId: string;
relyingPartyName: string;
origin: string;
userName: string;
userDisplayName: string;
email: string;
timeout: string;
attestation: string;
challenge: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we remove relyingPartyName, userName, userDisplayName and attestation?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the schema and we don't store them in the generated options. userName is the email and i renamed it to be more simple.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So ideally, the output of registerOptions function should be similar to the output of this function. For example, in the registerOptions output, we have:

user: {
                  id: string;
                  name: string; // user email
                  displayName: string; //user email
              };

Now in the output of getGeneratedOptions, it's totally different. So this is strange.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we only store what is needed to verify the credentials. See the schema here (webauthn_user_generated_options) https://docs.google.com/document/d/1G7tO9_dSNi8wur3ajGg4pq-wiHatKDbHv2sBt-uSbQg/edit?tab=t.0#heading=h.td4zxgo1675k

lib/ts/user.ts Outdated
@@ -90,6 +92,9 @@ export class User implements UserType {
id: string;
userId: string;
}[];
public readonly webauthn: {
credentialIds: string[];
}[];
Copy link
Author

@victorbojica victorbojica Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use webauthnCredentialId because there is no need to have a list of objects

lib/ts/recipe/webauthn/error.ts Show resolved Hide resolved
@@ -192,10 +192,13 @@ export type AccountInfo = {
id: string;
userId: string;
};
webauthn?: {
credentialIds: string[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also used by listUsersByAccount info. We may have to split types, or are there any cases where we'd want to search by multiple credentialIds? How does that function, do we only get a match if all credentialIds are present? Is there a use-case for that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't taken that into consideration. But as far as ai can tell, no. there is no use like that. There might the possibllity to search by one of the credentialIds. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think searching by a single credentialId makes sense.

lib/ts/recipe/webauthn/api/emailExists.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/api/recoverAccount.ts Outdated Show resolved Hide resolved
lib/ts/recipe/webauthn/api/utils.ts Outdated Show resolved Hide resolved
Comment on lines +225 to +234
LINKING_TO_SESSION_USER_FAILED: {
EMAIL_VERIFICATION_REQUIRED:
"Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_013)",
RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR:
"Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_014)",
ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR:
"Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_015)",
SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR:
"Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_016)",
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the error codes will have to be updated. You can add a TODO here and wherever an update like this is pending.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should be the update? Or am i missing something ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well the error codes are login method specific, so we'll have to have new ones I think.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. added to do.

}

const preAuthCheckRes = await AuthUtils.preAuthChecks({
authenticatingAccountInfo: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this have the credentialId? or is that something we only get later?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. We are identifying the user by email. And since we are signing up, the credential isn't stored yet.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so in no cases would this be relevant to this or other linking related functions? if so then the types may require some cleanup

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will check if we need to remove webauthn credential ids from the type - it is not used atm

Comment on lines 395 to 403
const verifyCredentialsResponse = await options.recipeImplementation.verifyCredentials({
credential,
webauthnGeneratedOptionsId,
tenantId,
userContext,
});
const checkCredentialsOnTenant = async () => {
return verifyCredentialsResponse.status === "OK";
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? is credential validation independent of the tenant?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Was doing it like this, because at the moment of implementation, we hadn't decided on having a method for retrieving the webauthGeneratedOptions (which contained the email) and i had to retrieve the email before calling AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired.
In the last few chats with Rishabh we deciede on adding the new method, but didn't update the API implmentation yet.

As a side note:
The credential can be shared across tenants and is not linked to a tenant.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So the point of checkCredentialsOnTenant is to find out if the user can use the current set of credentials to log in on the given tenantId (it can take it as a first parameter). This is tenant independent for pwless and thirdparty, but not for emailpassword for example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted to previous version because we don't need to check in the scope of the tenant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

};
}

const authenticatingUser = await AuthUtils.getAuthenticatingUserAndAddToCurrentTenantIfRequired({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function also needs to be updated to take the new account info prop into account (plus I think you'll need to check all places where we use the account info)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could have a chat about this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous comments. also need to check method to see if ti works with webauthn

}
// | SignUpErrorResponse
| { status: "EMAIL_ALREADY_EXISTS_ERROR" }
| { status: "WRONG_CREDENTIALS_ERROR" }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels weird on a signUp endpoint, maybe specify which param is wrong/how?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The status is a generic one, for whatever error happens because of the Webauthn credentials flow. If we'd need to be more specific here (not in the API implementation - we should keep it generic there), then we'd need to do the actual core implementation, as there might lots of stuff that we can only find out when we start implementing.

There is another type of status "INVALID_AUTHENTICATOR_ERROR" (along with the reason) which means that the credential has been generated by an "unsupported" authenticator (this could happen by overriding the registerOptions method and overriding the default values for attestation, requireResidentKey, residentKey, userVerification, supportedAlgorithmIds, etc.).

What do you think ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The INVALID_AUTHENTICATOR_ERROR sounds good. My main issue with WRONG_CREDENTIALS_ERROR is that it overlaps with the error status we have for people submitting wrong passwords I guess.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous error discussion

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@victorbojica victorbojica merged commit 6a2e53f into feat/webauthn/base Nov 25, 2024
16 of 18 checks passed
@victorbojica victorbojica deleted the feat/webauthn/basic-methods branch November 25, 2024 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants