├── .env-template ├── .gitignore ├── README.md ├── backend ├── CorbadoMailService.js ├── CorbadoService.js ├── CorbadoServiceExtension.js ├── CorbadoSmsService.js └── utils_backend.js └── frontend ├── PasskeyService.js └── utils_frontend.js /.env-template: -------------------------------------------------------------------------------- 1 | ORIGIN= 2 | API_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea/workspace.xml 3 | .idea/tasks.xml 4 | .idea/dictionaries 5 | .idea/vcs.xml 6 | .idea/jsLibraryMappings.xml 7 | .idea/ 8 | .idea 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Node.js 4 | 5 | This repository contains the sample files from the Node.js documentation. 6 | 7 | ### Frontend 8 | 9 | - **PasskeyService.js**: contains a frontend service to sign up and login, which mainly communicates with the backend 10 | and 11 | calls the WebAuthn API 12 | - **utils_frontend.js**: contains a helper function to check if the user's device can use passkeys 13 | 14 | ### Backend 15 | 16 | - **CorbadoService.js**: contains a backend service to provide basic passkeys sign up and login via Corbado API 17 | - **CorbadoServiceExtension.js**: contains a backend service to provide passkeys sign up (with email magic link confirmation) and 18 | login via Corbado API 19 | - **CorbadoEmailService.js**: contains a backend service that provides email magic link authentication via Corbado API 20 | - **CorbdoSmsService.js**: contains a backend service that provides SMS OTP authentication via Corbado API 21 | - **utils_backend.js**: contains a function to extract client information required for Corbado API -------------------------------------------------------------------------------- /backend/CorbadoMailService.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class CorbadoMailService { 4 | 5 | /** 6 | Initialization 7 | */ 8 | 9 | // @Route("/api/emailLinkSend") 10 | emailLinkSend = async (email, templateName, redirect, create, additionalPayload) => { 11 | let data = { 12 | email: email, 13 | templateName: templateName, 14 | redirect: process.env.ORIGIN + redirect, 15 | create: create, 16 | additionalPayload: JSON.stringify(additionalPayload) 17 | }; 18 | 19 | let res = await axios.post(process.env.API_URL + "emailLinks", data) 20 | 21 | return { 22 | httpStatusCode: res.data.httpStatusCode, message: res.data.message, 23 | }; 24 | }; 25 | 26 | 27 | /** 28 | Finalization 29 | */ 30 | 31 | // @Route("/api/emailLinkValidate/{emailLinkID}") 32 | emailLinkValidate = async (emailLinkID, token) => { 33 | let res = await axios.put(process.env.API_URL + "emailLinks/" + emailLinkID + "/validate", {token}); 34 | 35 | return { 36 | httpStatusCode: res.data.httpStatusCode, 37 | message: res.data.message, 38 | additionalPayload: res.data.additionalPayload, 39 | }; 40 | } 41 | } 42 | 43 | module.exports = new CorbadoMailService(); -------------------------------------------------------------------------------- /backend/CorbadoService.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class CorbadoService { 4 | 5 | /** 6 | Sign up 7 | */ 8 | 9 | /* Initialization phase */ 10 | 11 | // @Route("/api/signup/webauthn/init") 12 | startSignup = async (username, clientInfo) => { 13 | let {data} = await axios.post(process.env.API_URL + 'webauthn/register/start', { 14 | username, 15 | origin: process.env.ORIGIN, 16 | clientInfo: clientInfo, 17 | credentialStatus: "active" 18 | }); 19 | return data["publicKeyCredentialCreationOptions"]; 20 | }; 21 | 22 | 23 | /* Finalization phase */ 24 | 25 | // @Route("/api/signup/webauthn/finish") 26 | finishSignup = async (publicKeyCredential, clientInfo) => { 27 | let data = { 28 | publicKeyCredential: JSON.stringify(publicKeyCredential), 29 | origin: process.env.ORIGIN, 30 | clientInfo: clientInfo(), 31 | }; 32 | 33 | return axios.post(process.env.API_URL + 'webauthn/register/finish', data) 34 | } 35 | 36 | 37 | /** 38 | Login 39 | */ 40 | 41 | /* Initialization phase */ 42 | 43 | // @Route("/api/login/webauthn/start") 44 | startLogin = async (username, clientInfo) => { 45 | let {data} = await axios.post(process.env.API_URL + 'webauthn/authenticate/start', { 46 | username, origin: process.env.ORIGIN, clientInfo: clientInfo 47 | }); 48 | return data['publicKeyCredentialRequestOptions']; 49 | }; 50 | 51 | 52 | /* Finalization phase */ 53 | 54 | // @Route("/api/login/webauthn/finish") 55 | finishLogin = (publicKeyCredential, clientInfo) => { 56 | let data = { 57 | publicKeyCredential: JSON.stringify(publicKeyCredential), 58 | origin: process.env.ORIGIN, 59 | clientInfo: clientInfo, 60 | }; 61 | 62 | return axios.post(process.env.API_URL + 'webauthn/authenticate/finish', data); 63 | }; 64 | } 65 | 66 | module.exports = new CorbadoService(); -------------------------------------------------------------------------------- /backend/CorbadoServiceExtension.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class CorbadoServiceExtension { 4 | 5 | /** 6 | Add multiple devices 7 | */ 8 | 9 | 10 | /* Initialization phase */ 11 | 12 | // @Route("/api/signup/webauthn/init") 13 | startSignup = async (username, clientInfo) => { 14 | let {data} = await axios.post(process.env.API_URL + 'webauthn/register/start', { 15 | username, 16 | origin: process.env.ORIGIN, 17 | clientInfo: clientInfo 18 | }); 19 | return data["publicKeyCredentialCreationOptions"]; 20 | }; 21 | 22 | 23 | /* Finalization phase */ 24 | 25 | // @Route("/api/signup/webauthn/device/finish") 26 | finishSignupDevice = async (publicKeyCredential, clientInfo) => { 27 | let {data} = await this.webAuthnRegisterFinish(publicKeyCredential, clientInfo); 28 | return this.emailLinkSend( 29 | data["username"], 30 | 'webauthn_signup_device', 31 | process.env.REDIRECT, 32 | true, 33 | {credentialID: data["credentialID"]}); 34 | } 35 | 36 | webAuthnRegisterFinish = async (publicKeyCredential, clientInfo) => { 37 | let data = { 38 | publicKeyCredential: JSON.stringify(publicKeyCredential), 39 | origin: process.env.ORIGIN, 40 | clientInfo: clientInfo, 41 | }; 42 | 43 | return axios.post(process.env.API_URL + 'webauthn/register/finish', data); 44 | }; 45 | 46 | emailLinkSend = async (email, templateName, redirect, create, additionalPayload) => { 47 | let data = { 48 | email: email, 49 | templateName: templateName, // webauthn_signup_device 50 | redirect: process.env.ORIGIN + redirect, 51 | create: create, // true 52 | additionalPayload: JSON.stringify(additionalPayload) 53 | }; 54 | 55 | let res = await axios.post(process.env.API_URL + "emailLinks", data) 56 | 57 | let response = { 58 | httpStatusCode: res.data.httpStatusCode, 59 | message: res.data.message, 60 | }; 61 | 62 | return response; 63 | }; 64 | 65 | 66 | /* Confirmation phase */ 67 | 68 | // @Route("process.env.REDIRECT") 69 | confirmSignup = async (emailLinkId, token) => { 70 | let response = await this.emailLinkValidate(emailLinkId, token); 71 | let {credentialID} = JSON.parse(response.additionalPayload); 72 | return this.webAuthnConfirmDevice(credentialID, 'active'); 73 | } 74 | 75 | emailLinkValidate = async (emailLinkID, token) => { 76 | let res = await axios.put(process.env.API_URL + "emailLinks/" + emailLinkID + "/validate", {token}); 77 | 78 | let response = { 79 | httpStatusCode: res.data.httpStatusCode, 80 | message: res.data.message, 81 | additionalPayload: res.data.additionalPayload, 82 | }; 83 | 84 | return response; 85 | } 86 | 87 | webAuthnConfirmDevice = (credentialID, status) => { 88 | return axios.put(process.env.API_URL + `webauthn/credential/${credentialID}`, {status}); 89 | }; 90 | } 91 | 92 | module.exports = new CorbadoServiceExtension(); -------------------------------------------------------------------------------- /backend/CorbadoSmsService.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class CorbadoSmsService { 4 | 5 | /** 6 | Initialization 7 | */ 8 | 9 | // @Route("/api/smsCodeSend") 10 | smsCodeSend = async (phoneNumber, create) => { 11 | let data = { 12 | phoneNumber: phoneNumber, 13 | create: create, 14 | }; 15 | 16 | let res = await axios.post(process.env.API_URL + "smsCodes", data) 17 | 18 | return { 19 | httpStatusCode: res.data.httpStatusCode, 20 | message: res.data.message, 21 | smsCodeID: res.data.data.smsCodeID 22 | }; 23 | }; 24 | 25 | 26 | /** 27 | Finalization 28 | */ 29 | 30 | // @Route("/api/smsCodeValidate/{smsCodeID} 31 | smsCodeValidate = async (smsCodeID, smsCode) => { 32 | let res = await axios.put(process.env.API_URL + "smsCodes/" + smsCodeID + "/validate", {smsCode}); 33 | 34 | return { 35 | httpStatusCode: res.data.httpStatusCode, 36 | message: res.data.message, 37 | }; 38 | } 39 | } 40 | 41 | module.exports = new CorbadoSmsService(); -------------------------------------------------------------------------------- /backend/utils_backend.js: -------------------------------------------------------------------------------- 1 | exports.getClientInfo = (req) => { 2 | return { 3 | remoteAddress: req.headers['x-forwarded-for'] || req.socket.remoteAddress, 4 | userAgent: req.get('user-agent') 5 | }; 6 | }; -------------------------------------------------------------------------------- /frontend/PasskeyService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {create, get} from "@github/webauthn-json"; 3 | 4 | class PasskeyService { 5 | signUp(email) { 6 | return axios.post('/api/signup/webauthn/start', {email}) 7 | .then(async ({data}) => { 8 | let publicKeyCredential = await create(data); 9 | return axios.post('/api/signup/webauthn/finish', publicKeyCredential); 10 | }); 11 | } 12 | 13 | login(email) { 14 | return axios.post('api/login/webauthn/start', {email}) 15 | .then(async ({data}) => { 16 | let publicKeyCredential = await get(data); 17 | return axios.post('/api/login/webauthn/finish', publicKeyCredential); 18 | }) 19 | } 20 | } 21 | 22 | export default new PasskeyService(); -------------------------------------------------------------------------------- /frontend/utils_frontend.js: -------------------------------------------------------------------------------- 1 | exports.canUsePasskeys = () => { 2 | return new Promise(function (resolve, reject) { 3 | if (window.PublicKeyCredential) { 4 | return window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then((available) => { 5 | resolve(available); 6 | }).catch(() => { 7 | resolve(false); 8 | }) 9 | } else { 10 | return resolve(false); 11 | } 12 | }) 13 | }; --------------------------------------------------------------------------------