diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 74b8230598c3d4caec3763eb080a8aca02f4cf59..e4924294bb2daafc499aade9225bf197b1c006c6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -29,6 +29,7 @@
         "date-fns": "^2.24.0",
         "fetch-intercept": "^2.4.0",
         "http-proxy-middleware": "^2.0.1",
+        "i18n-iso-countries": "^6.8.0",
         "i18next": "^20.6.0",
         "i18next-browser-languagedetector": "^6.1.2",
         "i18next-http-backend": "^1.3.1",
@@ -8460,6 +8461,11 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "node_modules/diacritics": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
+      "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E="
+    },
     "node_modules/diff-sequences": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -11485,6 +11491,17 @@
         "node": ">=8.12.0"
       }
     },
+    "node_modules/i18n-iso-countries": {
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-6.8.0.tgz",
+      "integrity": "sha512-jJs/+CA6+VUICFxqGcB0vFMERGfhfvyNk+8Vb9EagSZkl7kSpm/kT0VyhvzM/zixDWEV/+oN9L7v/GT9BwzoGg==",
+      "dependencies": {
+        "diacritics": "1.3.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/i18next": {
       "version": "20.6.1",
       "resolved": "https://registry.npmjs.org/i18next/-/i18next-20.6.1.tgz",
@@ -16002,9 +16019,9 @@
       }
     },
     "node_modules/libphonenumber-js": {
-      "version": "1.9.37",
-      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz",
-      "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg=="
+      "version": "1.9.38",
+      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.38.tgz",
+      "integrity": "sha512-7CCl9NZPYtX4JNXdvV5RnrQqrXp7LsLOTpYSUfEJBiySEnC5hysdwouXAuWgPDB55D/fpKm4RjM2/tUUh8kuoA=="
     },
     "node_modules/lines-and-columns": {
       "version": "1.1.6",
@@ -31314,6 +31331,11 @@
         }
       }
     },
+    "diacritics": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
+      "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E="
+    },
     "diff-sequences": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -33610,6 +33632,14 @@
       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
       "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
     },
+    "i18n-iso-countries": {
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-6.8.0.tgz",
+      "integrity": "sha512-jJs/+CA6+VUICFxqGcB0vFMERGfhfvyNk+8Vb9EagSZkl7kSpm/kT0VyhvzM/zixDWEV/+oN9L7v/GT9BwzoGg==",
+      "requires": {
+        "diacritics": "1.3.0"
+      }
+    },
     "i18next": {
       "version": "20.6.1",
       "resolved": "https://registry.npmjs.org/i18next/-/i18next-20.6.1.tgz",
@@ -36935,9 +36965,9 @@
       }
     },
     "libphonenumber-js": {
-      "version": "1.9.37",
-      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz",
-      "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg=="
+      "version": "1.9.38",
+      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.38.tgz",
+      "integrity": "sha512-7CCl9NZPYtX4JNXdvV5RnrQqrXp7LsLOTpYSUfEJBiySEnC5hysdwouXAuWgPDB55D/fpKm4RjM2/tUUh8kuoA=="
     },
     "lines-and-columns": {
       "version": "1.1.6",
diff --git a/frontend/package.json b/frontend/package.json
index 8048554994014c225236c156172982cb61f3a8b6..206b6ac5a3f96ffe01f7d9aa4b1c1c4fba923630 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,13 +17,14 @@
     "@types/jest": "^26.0.24",
     "@types/node": "^12.20.24",
     "@types/react": "^17.0.20",
-    "@types/react-dom": "^17.0.9",
     "@types/react-datepicker": "^4.1.7",
+    "@types/react-dom": "^17.0.9",
     "@types/react-helmet": "^6.1.2",
     "@types/react-router-dom": "^5.1.8",
     "date-fns": "^2.24.0",
     "fetch-intercept": "^2.4.0",
     "http-proxy-middleware": "^2.0.1",
+    "i18n-iso-countries": "^6.8.0",
     "i18next": "^20.6.0",
     "i18next-browser-languagedetector": "^6.1.2",
     "i18next-http-backend": "^1.3.1",
diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
index b90a20a2fc719799c153614cde98b45b368a43ab..4a15b5d501f57377404d834f5f431c2d44ff02ac 100644
--- a/frontend/public/locales/en/common.json
+++ b/frontend/public/locales/en/common.json
@@ -19,7 +19,8 @@
     "comment": "Comment",
     "email": "E-mail",
     "fullName": "Full name",
-    "mobilePhone": "Mobile phone"
+    "mobilePhone": "Mobile phone",
+    "passportNumber": "Passport number"
   },
   "sponsor": {
     "contactInfo": "Contact information",
@@ -57,7 +58,8 @@
     "roleEndRequired": "Role end date is required",
     "emailRequired": "E-mail is required",
     "invalidMobilePhoneNumber": "Invalid phone number",
-    "invalidEmail": "Invalid e-mail address"
+    "invalidEmail": "Invalid e-mail address",
+    "passportNumberRequired": "Passport number required"
   },
   "button": {
     "back": "Back",
@@ -79,5 +81,13 @@
     "guestDepartment": "Department"
   },
   "yourGuests": "Your guests",
-  "registerNewGuest": "Register new guest"
+  "registerNewGuest": "Register new guest",
+  "guestOverview": "Guest overview",
+  "guestRegisterWizardText": {
+    "yourContactInformation": "Your contact information",
+    "contactInformationDescription": "Fill in your mobile phone number.",
+    "yourGuestPeriod": "Your guest period",
+    "guestPeriodDescription": "Period registered for your guest role."
+  },
+  "yourGuestAccount": "Your guest account"
 }
diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json
index a03c35a9cda2ca740f27c07b9d1dbf6dda309160..c6796f7f82168ff93ee043f6ebe7d64030d4f2ca 100644
--- a/frontend/public/locales/nb/common.json
+++ b/frontend/public/locales/nb/common.json
@@ -19,7 +19,8 @@
     "comment": "Kommentar",
     "email": "E-post",
     "fullName": "Fullt navn",
-    "mobilePhone": "Mobilnummer"
+    "mobilePhone": "Mobilnummer",
+    "passportNumber": "Passport number"
   },
   "sponsor": {
     "contactInfo": "Kontaktinformasjon",
@@ -56,7 +57,8 @@
     "roleEndRequired": "Sluttdato for rolle er obligatorisk",
     "emailRequired": "E-post er obligatorisk",
     "invalidMobilePhoneNumber": "Ugyldig telefonnummer",
-    "invalidEmail": "Ugyldig e-postadresse"
+    "invalidEmail": "Ugyldig e-postadresse",
+    "passportNumberRequired": "Passnummer er obligatorisk"
   },
   "button": {
     "back": "Tilbake",
@@ -78,5 +80,13 @@
     "guestDepartment": "Avdeling"
   },
   "yourGuests": "Dine gjester",
-  "registerNewGuest": "Registrer ny gjest"
+  "registerNewGuest": "Registrer ny gjest",
+  "guestOverview": "Oversikt over gjest",
+  "guestRegisterWizardText": {
+    "yourContactInformation": "Din kontaktinfo",
+    "contactInformationDescription": "Fyll inn ditt mobilnummer.",
+    "yourGuestPeriod": "Din gjesteperiode",
+    "guestPeriodDescription": "Registrert periode for din gjesterolle."
+  },
+  "yourGuestAccount": "Din gjestekonto"
 }
diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json
index 8ec2986b7089f2c73a41b3bf5fda5351c33b8161..779e929b9737d2aaf0275a7d0ed07b2711d9c7c9 100644
--- a/frontend/public/locales/nn/common.json
+++ b/frontend/public/locales/nn/common.json
@@ -20,7 +20,8 @@
     "comment": "Kommentar",
     "email": "E-post",
     "fullName": "Fullt namn",
-    "mobilePhone": "Mobilnummer"
+    "mobilePhone": "Mobilnummer",
+    "passportNumber": "Passport number"
   },
   "sponsor": {
     "contactInfo": "Kontaktinformasjon",
@@ -57,7 +58,8 @@
     "roleEndRequired": "Sluttdato for rolle er obligatorisk",
     "emailRequired": "E-post er obligatorisk",
     "invalidMobilePhoneNumber": "Ugyldig telefonnummer",
-    "invalidEmail": "Ugyldig e-postadresse"
+    "invalidEmail": "Ugyldig e-postadresse",
+    "passportNumberRequired": "Passnummer er obligatorisk"
   },
   "button": {
     "back": "Tilbake",
@@ -79,5 +81,13 @@
     "guestDepartment": "Avdeling"
   },
   "yourGuests": "Dine gjestar",
-  "registerNewGuest": "Registrer ny gjest"
+  "registerNewGuest": "Registrer ny gjest",
+  "guestOverview": "Oversikt over gjest",
+  "guestRegisterWizardText": {
+    "yourContactInformation": "Din kontaktinfo",
+    "contactInformationDescription": "Fyll inn ditt mobilnummer.",
+    "yourGuestPeriod": "Din gjesteperiode",
+    "guestPeriodDescription": "Registrert periode for di gjesterolle."
+  },
+  "yourGuestAccount": "Din gjestekonto"
 }
diff --git a/frontend/src/routes/components/overviewGuestButton.tsx b/frontend/src/routes/components/overviewGuestButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4111268f812c1eef908d905f2edec4db7fe81b24
--- /dev/null
+++ b/frontend/src/routes/components/overviewGuestButton.tsx
@@ -0,0 +1,58 @@
+import PersonIcon from '@mui/icons-material/Person'
+import { Box, IconButton } from '@mui/material'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { useHistory } from 'react-router-dom'
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'
+import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
+
+export default function OverviewGuestButton() {
+  const { t } = useTranslation(['common'])
+  const history = useHistory()
+
+  const goToGuestOverview = () => {
+    history.push('/guestregister')
+  }
+
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        flexDirection: 'row',
+        alignItems: 'baseline',
+        justifyContent: 'space-evenly',
+      }}
+    >
+      {/* TODO Where should the back arrow point to? */}
+      <ArrowBackIcon />
+
+      <Box
+        sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
+      >
+        <IconButton onClick={goToGuestOverview}>
+          <PersonIcon
+            fontSize="large"
+            sx={{
+              borderRadius: '2rem',
+              borderStyle: 'solid',
+              borderColor: 'primary.main',
+              fill: 'white',
+              backgroundColor: 'primary.main',
+            }}
+          />
+        </IconButton>
+        <Box
+          sx={{
+            typography: 'caption',
+          }}
+        >
+          {t('yourGuestAccount')}
+        </Box>
+      </Box>
+
+      <IconButton disabled>
+        <ArrowForwardIcon visibility="hidden" />
+      </IconButton>
+    </Box>
+  )
+}
diff --git a/frontend/src/routes/frontpage/index.tsx b/frontend/src/routes/frontpage/index.tsx
index 07ef806e04bef454ec1c815f45d1a97abb2562d2..54205f53e10c02996b76d37e64740be16c39277e 100644
--- a/frontend/src/routes/frontpage/index.tsx
+++ b/frontend/src/routes/frontpage/index.tsx
@@ -28,6 +28,9 @@ export default function FrontPage() {
             <li key="register">
               <Link to="/register/">Registration</Link>
             </li>
+            <li key="guestregister">
+              <Link to="/guestregister/">Guest Registration</Link>
+            </li>
           </ul>
         </p>
         <Debug />
diff --git a/frontend/src/routes/guest/register/authenticationMethod.ts b/frontend/src/routes/guest/register/authenticationMethod.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f01a26db655697443c98a7e2c6dd9266cb00b920
--- /dev/null
+++ b/frontend/src/routes/guest/register/authenticationMethod.ts
@@ -0,0 +1,9 @@
+/**
+ * Controls what is shown in the registration form
+ */
+enum AuthenticationMethod {
+  Feide,
+  Invite,
+}
+
+export default AuthenticationMethod
diff --git a/frontend/src/routes/guest/register/enteredGuestData.ts b/frontend/src/routes/guest/register/enteredGuestData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73b19b6600c0ea9d06b687986e56d2228a009e99
--- /dev/null
+++ b/frontend/src/routes/guest/register/enteredGuestData.ts
@@ -0,0 +1,10 @@
+/**
+ * This is data the guest has entered about himself. It is stored
+ * separate from ContactInformationBySponsor to make it clear that
+ * most of the data there the guest cannot change.
+ */
+export type EnteredGuestData = {
+  mobilePhone: string
+  nationalIdNumber?: string
+  passportNumber?: string
+}
diff --git a/frontend/src/routes/guest/register/guestDataForm.ts b/frontend/src/routes/guest/register/guestDataForm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44ab98237c271f3724206f00fe4891cae7278f88
--- /dev/null
+++ b/frontend/src/routes/guest/register/guestDataForm.ts
@@ -0,0 +1,26 @@
+/**
+ * This is data about the guest that the sponsor has entered when the invitation was created
+ */
+import AuthenticationMethod from './authenticationMethod'
+
+export type ContactInformationBySponsor = {
+  first_name: string
+  last_name: string
+  ou_name_nb: string
+  ou_name_en: string
+  role_name_en: string
+  role_name_nb: string
+  role_start: string
+  role_end: string
+  comment?: string
+
+  // These fields are in the form, but it is not expected that
+  // they are set, with the exception of e-mail, when the guest
+  // first follows the invite link
+  email?: string
+  mobile_phone?: string
+  fnr?: string
+  passport?: string
+
+  authentication_method: AuthenticationMethod
+}
diff --git a/frontend/src/routes/guest/register/guestRegisterCallableMethods.ts b/frontend/src/routes/guest/register/guestRegisterCallableMethods.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7f590e89facc2f43b97563241f34b9c7c92ea34
--- /dev/null
+++ b/frontend/src/routes/guest/register/guestRegisterCallableMethods.ts
@@ -0,0 +1,6 @@
+/**
+ * Contains methods that GuestRegister can call on in GuestRegisterStep
+ */
+export interface GuestRegisterCallableMethods {
+  doSubmit: () => void
+}
diff --git a/frontend/src/routes/guest/register/guestRegisterMethods.tsx b/frontend/src/routes/guest/register/guestRegisterMethods.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4ac5c2e5706cd0f9355c90d52a0e1c225587d6b
--- /dev/null
+++ b/frontend/src/routes/guest/register/guestRegisterMethods.tsx
@@ -0,0 +1,6 @@
+/**
+ * Contains methods that GuestRegisterStep can call on in GuestRegister
+ */
+export interface GuestRegisterMethods {
+  handleNext: () => void
+}
diff --git a/frontend/src/routes/guest/register/index.tsx b/frontend/src/routes/guest/register/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..23c4864ab59200324feb92ddf4227d8b6bc87cb9
--- /dev/null
+++ b/frontend/src/routes/guest/register/index.tsx
@@ -0,0 +1,182 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { Box, Button } from '@mui/material'
+import Page from 'components/page'
+
+import { useHistory } from 'react-router-dom'
+import OverviewGuestButton from '../../components/overviewGuestButton'
+import GuestRegisterStep from './registerPage'
+import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods'
+import { EnteredGuestData } from './enteredGuestData'
+import { ContactInformationBySponsor } from './guestDataForm'
+import AuthenticationMethod from './authenticationMethod'
+import { postJsonOpts } from '../../../utils'
+
+enum SubmitState {
+  NotSubmitted,
+  Submitted,
+  SubmittedError,
+}
+
+/*
+ * When the guest reaches this page he has already an invite ID set in his session.
+ */
+export default function GuestRegister() {
+  const { t } = useTranslation(['common'])
+  const history = useHistory()
+  // TODO On submit successful the user should be directed to some page telling h
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const [submitState, setSubmitState] = useState<SubmitState>(
+    SubmitState.NotSubmitted
+  )
+  const guestRegisterRef = useRef<GuestRegisterCallableMethods>(null)
+  const REGISTER_STEP = 0
+  // TODO Set step when user moves between pages
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const [activeStep, setActiveStep] = useState(0)
+
+  const [guestFormData, setGuestFormData] =
+    useState<ContactInformationBySponsor>({
+      first_name: '',
+      last_name: '',
+      ou_name_en: '',
+      ou_name_nb: '',
+      role_name_en: '',
+      role_name_nb: '',
+      role_start: '',
+      role_end: '',
+      comment: '',
+      email: '',
+      mobile_phone: '',
+      fnr: '',
+      passport: '',
+      authentication_method: AuthenticationMethod.Invite,
+    })
+
+  const guestContactInfo = async () => {
+    try {
+      const response = await fetch('/api/ui/v1/invited')
+
+      if (response.ok) {
+        response.json().then((jsonResponse) => {
+          console.log(`Guest data from server: ${JSON.stringify(jsonResponse)}`)
+
+          const authenticationMethod =
+            jsonResponse.meta.session_type === 'invite'
+              ? AuthenticationMethod.Invite
+              : AuthenticationMethod.Feide
+
+          setGuestFormData({
+            first_name: jsonResponse.person.first_name,
+            last_name: jsonResponse.person.last_name,
+            ou_name_en: jsonResponse.role.ou_name_en,
+            ou_name_nb: jsonResponse.role.ou_name_nb,
+            role_name_en: jsonResponse.role.role_name_en,
+            role_name_nb: jsonResponse.role.role_name_nb,
+            role_start: jsonResponse.role.start,
+            role_end: jsonResponse.role.end,
+            comment: jsonResponse.role.comments,
+
+            email: jsonResponse.person.email,
+            mobile_phone: jsonResponse.person.mobile_phone,
+            fnr: jsonResponse.fnr,
+            passport: jsonResponse.passport,
+
+            authentication_method: authenticationMethod,
+          })
+        })
+      }
+    } catch (error) {
+      console.log(error)
+    }
+  }
+
+  useEffect(() => {
+    guestContactInfo()
+  }, [])
+
+  const handleNext = () => {
+    // TODO Go to consent page
+    // setActiveStep((prevActiveStep) => prevActiveStep + 1)
+  }
+
+  const handleSave = () => {
+    if (activeStep === 0) {
+      if (guestRegisterRef.current) {
+        guestRegisterRef.current.doSubmit()
+      }
+    }
+  }
+
+  const handleForwardFromRegister = (
+    updateFormData: EnteredGuestData
+  ): void => {
+    const payload = {
+      person: {
+        mobile_phone: updateFormData.mobilePhone,
+        fnr: updateFormData.nationalIdNumber,
+        passport: updateFormData.passportNumber,
+      },
+    }
+
+    fetch('/api/ui/v1/invited/', postJsonOpts(payload))
+      .then((response) => {
+        if (response.ok) {
+          setSubmitState(SubmitState.Submitted)
+        } else {
+          setSubmitState(SubmitState.SubmittedError)
+          console.error(`Server responded with status: ${response.status}`)
+        }
+      })
+      .catch((error) => {
+        console.error(error)
+        setSubmitState(SubmitState.SubmittedError)
+      })
+  }
+
+  const handleCancel = () => {
+    history.push('/')
+  }
+
+  return (
+    <Page>
+      <OverviewGuestButton />
+      {/* Current page in wizard */}
+      <Box sx={{ width: '100%' }}>
+        {activeStep === REGISTER_STEP && (
+          <GuestRegisterStep
+            nextHandler={handleForwardFromRegister}
+            guestData={guestFormData}
+            ref={guestRegisterRef}
+          />
+        )}
+      </Box>
+
+      <Box
+        sx={{
+          display: 'flex',
+          flexDirection: 'row',
+          pt: 2,
+          color: 'primary.main',
+          paddingBottom: '1rem',
+        }}
+      >
+        {activeStep === REGISTER_STEP && (
+          <Button
+            data-testid="button-next"
+            sx={{ color: 'theme.palette.secondary', mr: 1 }}
+            onClick={handleNext}
+          >
+            {t('button.next')}
+          </Button>
+        )}
+
+        <Button onClick={handleCancel}>{t('button.cancel')}</Button>
+
+        {/* TODO This button is only for testing while developing */}
+        <Button onClick={handleSave}>{t('button.save')}</Button>
+      </Box>
+    </Page>
+  )
+}
diff --git a/frontend/src/routes/guest/register/registerPage.test.tsx b/frontend/src/routes/guest/register/registerPage.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ccf4cfc8af9af38370d1c12f3b008adcc9a7ad26
--- /dev/null
+++ b/frontend/src/routes/guest/register/registerPage.test.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import { render, screen, waitFor } from 'test-utils'
+import AdapterDateFns from '@mui/lab/AdapterDateFns'
+import { LocalizationProvider } from '@mui/lab'
+import GuestRegisterStep from './registerPage'
+import { EnteredGuestData } from './enteredGuestData'
+import AuthenticationMethod from './authenticationMethod'
+
+test('Guest register page showing passport field on manual registration', async () => {
+  const nextHandler = (enteredGuestData: EnteredGuestData) => {
+    console.log(`Entered data: ${enteredGuestData}`)
+  }
+  const guestData = {
+    first_name: '',
+    last_name: '',
+    ou_name_en: '',
+    ou_name_nb: '',
+    role_name_en: '',
+    role_name_nb: '',
+    role_start: '',
+    role_end: '',
+    comment: '',
+    email: '',
+    mobile_phone: '',
+    fnr: '',
+    passport: '',
+    authentication_method: AuthenticationMethod.Invite,
+  }
+
+  render(
+    <LocalizationProvider dateAdapter={AdapterDateFns}>
+      <GuestRegisterStep nextHandler={nextHandler} guestData={guestData} />
+    </LocalizationProvider>
+  )
+
+  await waitFor(() => {
+    expect(screen.queryByTestId('passport_number_input')).toBeInTheDocument()
+  })
+})
diff --git a/frontend/src/routes/guest/register/registerPage.tsx b/frontend/src/routes/guest/register/registerPage.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..32180f46f4b04bdcde55775d5e272641b8d21a16
--- /dev/null
+++ b/frontend/src/routes/guest/register/registerPage.tsx
@@ -0,0 +1,251 @@
+import {
+  Box,
+  MenuItem,
+  Select,
+  SelectChangeEvent,
+  Stack,
+  TextField,
+  Typography,
+} from '@mui/material'
+import { SubmitHandler, useForm } from 'react-hook-form'
+import React, { forwardRef, Ref, useImperativeHandle, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+  CountryCallingCode,
+  getCountries,
+  getCountryCallingCode,
+} from 'libphonenumber-js'
+import { getName } from 'i18n-iso-countries'
+import { ContactInformationBySponsor } from './guestDataForm'
+import { EnteredGuestData } from './enteredGuestData'
+import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods'
+import { isValidFnr, isValidMobilePhoneNumber } from '../../../utils'
+import AuthenticationMethod from './authenticationMethod'
+
+interface GuestRegisterProperties {
+  nextHandler(guestData: EnteredGuestData): void
+
+  guestData: ContactInformationBySponsor
+}
+
+/**
+ * This component is the form where the guest enters missing information about himself and
+ * where he can see the data the sponsor has entered and the role. The page may also
+ * be populated with data from a third-party like Feide if the guest logged in using that.
+ */
+const GuestRegisterStep = forwardRef(
+  (props: GuestRegisterProperties, ref: Ref<GuestRegisterCallableMethods>) => {
+    const { i18n, t } = useTranslation(['common'])
+    const { nextHandler, guestData } = props
+
+    const [countryCode, setCountryCode] = useState<
+      CountryCallingCode | undefined
+    >(undefined)
+
+    const handleCountryCodeChange = (event: SelectChangeEvent) => {
+      if (event.target.value) {
+        const countryCodeType = getCountries().find(
+          (value) => value.toString() === event.target.value
+        )
+        if (countryCodeType) {
+          setCountryCode(getCountryCallingCode(countryCodeType))
+        }
+      } else {
+        setCountryCode(undefined)
+      }
+    }
+
+    const submit: SubmitHandler<EnteredGuestData> = (data) => {
+      nextHandler(data)
+    }
+
+    const {
+      register,
+      handleSubmit,
+      setValue,
+      trigger,
+      formState: { errors },
+    } = useForm()
+    const onSubmit = handleSubmit<EnteredGuestData>(submit)
+
+    function doSubmit() {
+      return onSubmit()
+    }
+
+    register('mobilePhone', {
+      required: t<string>('validation.roleTypeRequired'),
+      validate: isValidMobilePhoneNumber,
+    })
+
+    useImperativeHandle(ref, () => ({ doSubmit }))
+
+    return (
+      <>
+        <Typography
+          variant="h5"
+          sx={{
+            paddingTop: '1rem',
+            paddingBottom: '1rem',
+          }}
+        >
+          {t('guestRegisterWizardText.yourContactInformation')}
+        </Typography>
+        <Typography sx={{ paddingBottom: '2rem' }}>
+          {t('guestRegisterWizardText.contactInformationDescription')}
+        </Typography>
+        <Box sx={{ maxWidth: '30rem' }}>
+          <form onSubmit={onSubmit}>
+            <Stack spacing={2}>
+              <TextField
+                id="firstName"
+                label={t('input.firstName')}
+                value={guestData.first_name}
+                disabled
+              />
+              <TextField
+                id="lastName"
+                label={t('input.lastName')}
+                value={guestData.last_name}
+                disabled
+              />
+
+              <TextField
+                id="email"
+                label={t('input.email')}
+                value={guestData.email}
+                disabled
+              />
+
+              <Box
+                sx={{
+                  display: 'flex',
+                  flexDirection: 'row',
+                  paddingBottom: '2rem',
+                }}
+              >
+                <Select
+                  sx={{
+                    maxHeight: '2.5rem',
+                    minWidth: '5rem',
+                    marginRight: '0.5rem',
+                  }}
+                  labelId="phone-number-select"
+                  id="phone-number-select"
+                  onChange={handleCountryCodeChange}
+                >
+                  {getCountries().map((country) => (
+                    <MenuItem key={country} value={country}>
+                      {getName(country, i18n.language)} +
+                      {getCountryCallingCode(country)}
+                    </MenuItem>
+                  ))}
+                </Select>
+
+                <TextField
+                  sx={{ flexGrow: 2 }}
+                  label={t('input.mobilePhone')}
+                  error={!!errors.mobilePhone}
+                  helperText={errors.mobilePhone && errors.mobilePhone.message}
+                  onChange={(value) => {
+                    if (countryCode) {
+                      // The country code and the rest of the mobile number are in two fields, so cannot
+                      // register the field directly in form, but need to have extra logic defined
+                      // to combine the values before writing them to the form handling
+                      setValue(
+                        'mobilePhone',
+                        `+${countryCode.toString()}${value.target.value}`
+                      )
+                      trigger('mobilePhone')
+                    }
+                  }}
+                />
+              </Box>
+
+              {guestData.authentication_method ===
+                AuthenticationMethod.Invite && (
+                <>
+                  <TextField
+                    id="passport"
+                    data-testid="passport_number_input"
+                    label={t('input.passportNumber')}
+                    {...register('passportNumber', {
+                      required: t<string>('validation.passportNumberRequired'),
+                    })}
+                  />
+
+                  <TextField
+                    id="national_id_number"
+                    label={t('input.nationalIdNumber')}
+                    error={!!errors.national_id_number}
+                    helperText={
+                      errors.nationalIdNumber && errors.nationalIdNumber.message
+                    }
+                    {...register('nationalIdNumber', {
+                      validate: isValidFnr,
+                    })}
+                  />
+                </>
+              )}
+
+              {guestData.authentication_method ===
+                AuthenticationMethod.Feide && (
+                <TextField
+                  id="national_id_number"
+                  data-testid="national_id_number_feide"
+                  label={t('input.nationalIdNumber')}
+                  disabled
+                />
+              )}
+
+              <Typography variant="h5" sx={{ paddingTop: '1rem' }}>
+                {t('guestRegisterWizardText.yourGuestPeriod')}
+              </Typography>
+              <Typography sx={{ paddingBottom: '1rem' }}>
+                {t('guestRegisterWizardText.guestPeriodDescription')}
+              </Typography>
+              <TextField
+                id="ou-unit"
+                value={
+                  i18n.language === 'en'
+                    ? guestData.ou_name_en
+                    : guestData.ou_name_nb
+                }
+                label={t('ou')}
+                disabled
+              />
+
+              <TextField
+                id="roleType"
+                label={t('input.roleType')}
+                value={
+                  i18n.language === 'en'
+                    ? guestData.role_name_en
+                    : guestData.role_name_nb
+                }
+                disabled
+              />
+
+              <TextField
+                id="rolePeriod"
+                label={t('period')}
+                value={`${guestData.role_start} - ${guestData.role_end}`}
+                disabled
+              />
+
+              <TextField
+                id="comment"
+                label={t('input.comment')}
+                multiline
+                rows={5}
+                value={guestData.comment}
+                disabled
+              />
+            </Stack>
+          </form>
+        </Box>
+      </>
+    )
+  }
+)
+
+export default GuestRegisterStep
diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx
index a62d0d7f75f6ae2da3f547e464fa2093ebdaca80..40b95593383c15ecec08a67feddc1c8e1d83bc7e 100644
--- a/frontend/src/routes/index.tsx
+++ b/frontend/src/routes/index.tsx
@@ -16,6 +16,11 @@ import Footer from 'routes/components/footer'
 import Header from 'routes/components/header'
 import NotFound from 'routes/components/notFound'
 import ProtectedRoute from 'components/protectedRoute'
+import { registerLocale } from 'i18n-iso-countries'
+import i18n_iso_countries_en from 'i18n-iso-countries/langs/en.json'
+import i18n_iso_countries_nb from 'i18n-iso-countries/langs/nb.json'
+import i18n_iso_countries_nn from 'i18n-iso-countries/langs/nn.json'
+import GuestRegister from './guest/register'
 
 const AppWrapper = styled('div')({
   display: 'flex',
@@ -28,6 +33,11 @@ const AppWrapper = styled('div')({
 export default function App() {
   const { user, clearUserInfo } = useUserContext()
 
+  // Load country names for the supported languages
+  registerLocale(i18n_iso_countries_en)
+  registerLocale(i18n_iso_countries_nb)
+  registerLocale(i18n_iso_countries_nn)
+
   // Intercept fetch responses
   fetchIntercept.register({
     response(response) {
@@ -56,6 +66,7 @@ export default function App() {
           </ProtectedRoute>
           <Route path="/invite/:id" component={InviteLink} />
           <Route path="/invite/" component={Invite} />
+          <Route path="/guestregister/" component={GuestRegister} />
           <Route>
             <NotFound />
           </Route>
diff --git a/frontend/src/routes/invite/index.tsx b/frontend/src/routes/invite/index.tsx
index 6d44123163867ae9b567e21122f7e23ce21dc167..2edc66658d9b3f67443f542e25f704c525a7fcf1 100644
--- a/frontend/src/routes/invite/index.tsx
+++ b/frontend/src/routes/invite/index.tsx
@@ -1,5 +1,6 @@
 import Page from 'components/page'
 import { useUserContext } from 'contexts'
+import { Link } from 'react-router-dom'
 
 function Invite() {
   const { user } = useUserContext()
@@ -11,6 +12,9 @@ function Invite() {
         TODO: Put information about login options, and buttons to them on this
         page
       </p>
+      After login or when clicking on the manual registration option, the user
+      should be sent here:
+      <Link to="/guestregister/">Guest Registration</Link>
     </Page>
   )
 }
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index e6eea48ec3a5174b3b171077dea705879f06964f..e07c88c1cd1330d19175e915367a2abe801d688b 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -268,3 +268,5 @@ SCHEDULE_TASKS = {
 
 INSTANCE_NAME = "local"
 INTERNAL_RK_PREFIX = "no.{instance}.greg".format(instance=INSTANCE_NAME)
+
+FEIDE_SOURCE = "feide"
diff --git a/gregui/api/serializers/guest.py b/gregui/api/serializers/guest.py
index 20a8759264756d36d231509342a3e864af683a46..ce702752a1b1d0613bd339a1d34e965a8909290b 100644
--- a/gregui/api/serializers/guest.py
+++ b/gregui/api/serializers/guest.py
@@ -1,27 +1,51 @@
+import phonenumbers
 from rest_framework import serializers
 
 from greg.models import Identity, Person
+from greg.utils import is_valid_norwegian_national_id_number
+
+
+def _validateNorwegianNationalIdNumber(value):
+    # Not excepted that D-numbers will be entered through the form, so only
+    # accept national ID numbers
+    if not is_valid_norwegian_national_id_number(value, False):
+        raise serializers.ValidationError("Not a valid Norwegian national ID number")
+
+
+def _validatePhoneNumber(value):
+    if not phonenumbers.is_valid_number(phonenumbers.parse(value)):
+        raise serializers.ValidationError("Invalid phone number")
 
 
 class GuestRegisterSerializer(serializers.ModelSerializer):
-    first_name = serializers.CharField(required=True)
-    last_name = serializers.CharField(required=True)
-    email = serializers.CharField(required=True)
-    mobile_phone = serializers.CharField(required=True)
+    # TODO first_name and last_name set as not required to throwing an exception if they are not included in what is sent back from the frontend. It is perhaps not required that they are in the reponse from the client if the guest should be allowed to change them
+    first_name = serializers.CharField(required=False)
+    last_name = serializers.CharField(required=False)
+    # E-mail set to not required to avoid raising exception if it is not included in input. It is not given that
+    # the guest should be allowed to update it
+    email = serializers.CharField(required=False)
+    mobile_phone = serializers.CharField(
+        required=True, validators=[_validatePhoneNumber]
+    )
+    fnr = serializers.CharField(
+        required=False, validators=[_validateNorwegianNationalIdNumber]
+    )
 
     def update(self, instance, validated_data):
-        email = validated_data.pop("email")
         mobile_phone = validated_data.pop("mobile_phone")
 
-        if not instance.private_email:
-            Identity.objects.create(
-                person=instance,
-                type=Identity.IdentityType.PRIVATE_EMAIL,
-                value=email,
-            )
-        else:
-            instance.private_email.value = email
-            instance.private_email.save()
+        if "email" in validated_data:
+            email = validated_data.pop("email")
+            if not instance.private_email:
+                Identity.objects.create(
+                    person=instance,
+                    type=Identity.IdentityType.PRIVATE_EMAIL,
+                    value=email,
+                )
+            else:
+                private_email = instance.private_email
+                private_email.value = email
+                private_email.save()
 
         if not instance.private_mobile:
             Identity.objects.create(
@@ -30,17 +54,32 @@ class GuestRegisterSerializer(serializers.ModelSerializer):
                 value=mobile_phone,
             )
         else:
-            instance.private_mobile.value = mobile_phone
-            instance.private_mobile.save()
+            private_mobile = instance.private_mobile
+            private_mobile.value = mobile_phone
+            private_mobile.save()
+
+        if "fnr" in validated_data:
+            fnr = validated_data.pop("fnr")
+            if not instance.fnr:
+                Identity.objects.create(
+                    person=instance,
+                    type=Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER,
+                    value=fnr,
+                )
+            else:
+                fnr_existing = instance.fnr
+                fnr_existing.value = fnr
+                fnr_existing.save()
 
         # TODO: we only want to allow changing the name if we don't have one
         #       from a reliable source (Feide/KORR)
-        instance.first_name = validated_data["first_name"]
-        instance.last_name = validated_data["last_name"]
+        # TODO Comment back in after it is decided if name updates are allowed
+        # instance.first_name = validated_data["first_name"]
+        # instance.last_name = validated_data["last_name"]
 
         return instance
 
     class Meta:
         model = Person
-        fields = ("id", "first_name", "last_name", "email", "mobile_phone")
+        fields = ("id", "first_name", "last_name", "email", "mobile_phone", "fnr")
         read_only_fields = ("id",)
diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py
index 176976302487930c0f415d87363b6f0002db152f..2432ba0dede5c942bf325f90ee38f35656299ad8 100644
--- a/gregui/api/views/invitation.py
+++ b/gregui/api/views/invitation.py
@@ -1,25 +1,21 @@
-import json
-import datetime
-from uuid import uuid4
+from enum import Enum
+
 from django.core import exceptions
 from django.db import transaction
 from django.http.response import JsonResponse
-
 from django.utils import timezone
-from rest_framework import serializers, status
+from rest_framework import status
 from rest_framework.authentication import SessionAuthentication, BasicAuthentication
 from rest_framework.generics import CreateAPIView, GenericAPIView
 from rest_framework.parsers import JSONParser
-from rest_framework.permissions import AllowAny, IsAuthenticated
+from rest_framework.permissions import AllowAny
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from greg.models import Identity, Invitation, InvitationLink, Person, Role, Sponsor
+from greg.models import Identity, InvitationLink
 from greg.permissions import IsSponsor
 from gregui.api.serializers.guest import GuestRegisterSerializer
 from gregui.api.serializers.invitation import InviteGuestSerializer
-
-
 from gregui.models import GregUserProfile
 
 
@@ -101,12 +97,21 @@ class CheckInvitationView(APIView):
         return Response(status=status.HTTP_200_OK)
 
 
+class SessionType(Enum):
+    INVITE = "invite"
+    FEIDE = "feide"
+
+
 class InvitedGuestView(GenericAPIView):
     authentication_classes = [SessionAuthentication, BasicAuthentication]
+    # The endpoint is only for invited guests, but the authorization happens in the actual method
     permission_classes = [AllowAny]
     parser_classes = [JSONParser]
     serializer_class = GuestRegisterSerializer
 
+    # TODO Update to make dynamic based on where we get the information from. If we get some from Feide, then the user should not be allowed to change it
+    fields_allowed_to_update = ["email", "fnr", "mobile_phone", "passport"]
+
     def get(self, request, *args, **kwargs):
         """
         Endpoint for fetching data related to an invite
@@ -129,12 +134,24 @@ class InvitedGuestView(GenericAPIView):
         person = role.person
         sponsor = role.sponsor_id
 
+        # If the user is not logged in then tell the client to take him through the manual registration process
+        session_type = (
+            SessionType.INVITE.value
+            if request.user.is_anonymous
+            else SessionType.FEIDE.value
+        )
+
         try:
-            fnr = person.identities.get(type="norwegian_national_id_number").value
+            fnr = person.identities.get(
+                type=Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER
+            ).value
         except Identity.DoesNotExist:
             fnr = None
+
         try:
-            passport = person.identities.get(type="passport_number").value
+            passport = person.identities.get(
+                type=Identity.IdentityType.PASSPORT_NUMBER
+            ).value
         except Identity.DoesNotExist:
             passport = None
 
@@ -160,6 +177,7 @@ class InvitedGuestView(GenericAPIView):
                 "end": role.end_date,
                 "comments": role.comments,
             },
+            "meta": {"session_type": session_type},
         }
         return JsonResponse(data=data, status=status.HTTP_200_OK)
 
@@ -172,7 +190,6 @@ class InvitedGuestView(GenericAPIView):
         the guest.
         """
         invite_id = request.session.get("invite_id")
-        data = request.data
 
         # Ensure the invitation link is valid and not expired
         try:
@@ -184,8 +201,21 @@ class InvitedGuestView(GenericAPIView):
 
         person = invite_link.invitation.role.person
 
+        data = request.data
+
+        if self._verified_fnr_already_exists(person) and "fnr" in data:
+            # The user should not be allowed to change a verified fnr
+            return Response(status=status.HTTP_400_BAD_REQUEST)
+
+        if not self._only_allowed_fields_in_request(data):
+            return Response(status=status.HTTP_400_BAD_REQUEST)
+
         with transaction.atomic():
-            serializer = self.get_serializer(instance=person, data=request.data)
+            # Note this only serializes data for the person, it does not look at other sections
+            # in the response that happen to be there
+            serializer = self.get_serializer(
+                instance=person, data=request.data["person"]
+            )
             serializer.is_valid(raise_exception=True)
             person = serializer.save()
 
@@ -198,3 +228,22 @@ class InvitedGuestView(GenericAPIView):
             invite_link.save()
             # TODO: Send an email to the sponsor?
         return Response(status=status.HTTP_200_OK)
+
+    def _verified_fnr_already_exists(self, person) -> bool:
+        try:
+            person.identities.get(
+                type=Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER,
+                verified=Identity.Verified.AUTOMATIC,
+            )
+            return True
+        except Identity.DoesNotExist:
+            return False
+
+    def _only_allowed_fields_in_request(self, request_data) -> bool:
+        # Check how many of the allowed fields are filled in
+        person_data = request_data["person"]
+        number_of_fields_filled_in = sum(
+            map(lambda x: x in person_data.keys(), self.fields_allowed_to_update)
+        )
+        # Check that there are no other fields filled in
+        return number_of_fields_filled_in == len(person_data.keys())
diff --git a/gregui/authentication/auth_backends.py b/gregui/authentication/auth_backends.py
index 023092bf6a8368f66243efab385e4cb3331bd9e4..74b897c1de1035368d5977430e2aa4224348273d 100644
--- a/gregui/authentication/auth_backends.py
+++ b/gregui/authentication/auth_backends.py
@@ -6,6 +6,8 @@ from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth.backends import BaseBackend
 from django.core.exceptions import SuspiciousOperation
+from django.utils import timezone
+
 from mozilla_django_oidc.auth import OIDCAuthenticationBackend
 
 from greg.models import Identity, Person, Sponsor
@@ -237,18 +239,24 @@ class GregOIDCBackend(ValidatingOIDCBackend):
                 # Find or create person, and add identity
                 person = self._get_or_create_person(userinfo)
                 identity = Identity(
-                    type="feide_id", value=userinfo["userid_feide"], person=person
+                    type=Identity.IdentityType.FEIDE_ID,
+                    value=userinfo["userid_feide"],
+                    person=person,
+                    source=settings.FEIDE_SOURCE,
+                    verified=Identity.Verified.AUTOMATIC,
+                    verified_at=timezone.now(),
                 )
                 identity.save()
 
             try:
-                email_identity = Identity.objects.get(
-                    type="private_email", value=userinfo["email"]
-                )
+                Identity.objects.get(type="private_email", value=userinfo["email"])
             except Identity.DoesNotExist:
                 # Add email if missing
                 email_identity = Identity(
-                    type="private_email", value=userinfo["email"], person=person
+                    type="private_email",
+                    value=userinfo["email"],
+                    person=person,
+                    source=settings.FEIDE_SOURCE,
                 )
                 email_identity.save()
 
diff --git a/gregui/tests/api/test_invitation.py b/gregui/tests/api/test_invitation.py
index 5a0f3a95b9a8eb9b7ec504749b5dc32bacb4d50d..e3f8d7a972ea4304a0558ca87644bd279c377547 100644
--- a/gregui/tests/api/test_invitation.py
+++ b/gregui/tests/api/test_invitation.py
@@ -4,7 +4,7 @@ from rest_framework import status
 from rest_framework.reverse import reverse
 from rest_framework.test import APIClient
 
-from greg.models import InvitationLink, Person
+from greg.models import InvitationLink, Person, Identity
 
 
 @pytest.mark.django_db
@@ -108,8 +108,8 @@ def test_invited_guest_can_post_information(
 
     # post updated info to confirm from guest
     new_email = "private@example.org"
-    new_phone = "+4712345678"
-    data = dict(email=new_email, mobile_phone=new_phone, **person_foo_data)
+    new_phone = "+4797543992"
+    data = dict(person=dict(email=new_email, mobile_phone=new_phone))
     response = client.post(
         reverse("gregui-v1:invited-info"),
         data,
@@ -120,7 +120,6 @@ def test_invited_guest_can_post_information(
 
     # Check that the object was updated in the database
     assert Person.objects.count() == 1
-    assert person.private_email.value == new_email
     assert person.private_mobile.value == new_phone
 
 
@@ -155,3 +154,82 @@ def test_post_invited_info_deleted_inv_link(client, invitation_link):
     # invitation link
     response = client.post(reverse("gregui-v1:invited-info"))
     assert response.status_code == status.HTTP_403_FORBIDDEN
+
+
+@pytest.mark.django_db
+def test_post_invited_info_invalid_national_id_number(
+    client, invitation_link, person_foo_data, person
+):
+    data = {
+        "person": {
+            "mobile_phone": "+4707543001",
+            "email": "test@example.com",
+            "fnr": "123",
+        }
+    }
+    url = reverse("gregui-v1:invited-info")
+
+    assert person.fnr is None
+
+    session = client.session
+    session["invite_id"] = str(invitation_link.uuid)
+    session.save()
+
+    response = client.post(url, data, format="json")
+
+    # The request should fail and the fnr-property should stay unchanged
+    assert response.status_code == status.HTTP_400_BAD_REQUEST
+    person.refresh_from_db()
+    assert person.fnr is None
+
+
+@pytest.mark.django_db
+def test_post_invited_info_valid_national_id_number(
+    client, invitation_link, person_foo_data, person
+):
+    fnr = "11120618212"
+    data = {
+        "person": {
+            "mobile_phone": "+4797543992",
+            "email": "test@example.com",
+            "fnr": fnr,
+        }
+    }
+    url = reverse("gregui-v1:invited-info")
+
+    assert person.fnr is None
+
+    session = client.session
+    session["invite_id"] = str(invitation_link.uuid)
+    session.save()
+
+    response = client.post(url, data, format="json")
+
+    assert response.status_code == status.HTTP_200_OK
+    person.refresh_from_db()
+    assert person.fnr.value == fnr
+
+
+@pytest.mark.django_db
+def test_email_update(client, invitation_link, person_foo_data, person):
+    email_test = "test@example.com"
+    url = reverse("gregui-v1:invited-info")
+
+    Identity.objects.create(
+        person=person,
+        type=Identity.IdentityType.PRIVATE_EMAIL,
+        value=email_test,
+    )
+    person.private_email.refresh_from_db()
+    assert person.private_email.value == email_test
+
+    session = client.session
+    session["invite_id"] = str(invitation_link.uuid)
+    session.save()
+
+    data = {"person": {"mobile_phone": "+4797543992", "email": "test2@example.com"}}
+    response = client.post(url, data, format="json")
+    assert response.status_code == status.HTTP_200_OK
+
+    person.private_email.refresh_from_db()
+    assert person.private_email.value == "test2@example.com"
diff --git a/poetry.lock b/poetry.lock
index cd03c6a965a0f12b8fc0ddd783ba6124c5c2da08..7dea2fedbbd3cac0e09340d3a972477a749b4dd3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -741,6 +741,14 @@ python-versions = "*"
 [package.dependencies]
 ptyprocess = ">=0.5"
 
+[[package]]
+name = "phonenumbers"
+version = "8.12.35"
+description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
+category = "main"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "pickleshare"
 version = "0.7.5"
@@ -1259,7 +1267,7 @@ python-versions = "*"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "b964658143e61764cbb714c40fe6227158d260a426c2722ff84f813421f12d24"
+content-hash = "8305dd9a41a84f4bc1a58e28d024a6d8d117c628191b63cec54a1199cd97fd99"
 
 [metadata.files]
 ansicon = [
@@ -1410,6 +1418,7 @@ coverage = [
 ]
 cryptography = [
     {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"},
+    {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"},
     {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"},
     {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"},
     {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"},
@@ -1663,6 +1672,10 @@ pexpect = [
     {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
     {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
 ]
+phonenumbers = [
+    {file = "phonenumbers-8.12.35-py2.py3-none-any.whl", hash = "sha256:16c8f6d682ab5be550af2f4a2f81f0a90c8743e37babc7465edcefc106d0a1eb"},
+    {file = "phonenumbers-8.12.35.tar.gz", hash = "sha256:f426d419aabf6366c27ef1193918cc55217ef0e8be8f09cbf0667131037ca229"},
+]
 pickleshare = [
     {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
     {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
@@ -1829,18 +1842,26 @@ pyyaml = [
     {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
     {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
     {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
     {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
     {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
     {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
     {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
     {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
     {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
     {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
     {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
     {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
@@ -1850,7 +1871,9 @@ redis = [
     {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
 ]
 regex = [
+    {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:094a905e87a4171508c2a0e10217795f83c636ccc05ddf86e7272c26e14056ae"},
     {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"},
+    {file = "regex-2021.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0f2f874c6a157c91708ac352470cb3bef8e8814f5325e3c5c7a0533064c6a24"},
     {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"},
     {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"},
     {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"},
@@ -1874,7 +1897,9 @@ regex = [
     {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"},
     {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"},
     {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"},
+    {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19b8f6d23b2dc93e8e1e7e288d3010e58fafed323474cf7f27ab9451635136d9"},
     {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"},
+    {file = "regex-2021.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:951be934dc25d8779d92b530e922de44dda3c82a509cdb5d619f3a0b1491fafa"},
     {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"},
     {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"},
     {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"},
@@ -1882,7 +1907,9 @@ regex = [
     {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"},
     {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"},
     {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"},
+    {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6dcf53d35850ce938b4f044a43b33015ebde292840cef3af2c8eb4c860730fff"},
     {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"},
+    {file = "regex-2021.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2ec1c106d3f754444abf63b31e5c4f9b5d272272a491fa4320475aba9e8157c"},
     {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"},
     {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"},
     {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"},
diff --git a/pyproject.toml b/pyproject.toml
index 9d78cee33085524aa154086a7b0b97686b95f3a8..cfa3ad5cc3418cad26018319c0447cd429843c91 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,6 +25,7 @@ django-reversion = "*"
 django-sesame = {extras = ["ua"], version = "^2.4"}
 mozilla-django-oidc = "^2.0.0"
 django-q = "^1.3.9"
+phonenumbers = "^8.12.35"
 
 [tool.poetry.dev-dependencies]
 Faker = "*"