arrow_backDocs

Step 4

Login the voter.

After the election is loaded and confirmed live, the voter can sign in with their voter ID and password. CrownVote returns the voter profile, access token, and ballots.

Use voterId and password

Remote voting always uses voterId and password in this beginner guide.

Store the access token

The returned token is used later when submitting the vote.

Keep ballots in state

The login response includes ballots, so no extra ballot request is needed.

Do not render yet

This page stores ballots. The next page renders them into selectable UI.

code POST /voter/login
POST /api/v1/voter/login HTTP/1.1
Host: api.crownvote.com
Content-Type: application/json
X-PUBLIC-KEY: cv_pub_xxxxx

{
  "voterId": "236165",
  "password": "voter-password"
}
code Login response
{
  "status": true,
  "data": {
    "accessToken": {
      "type": "Bearer",
      "value": "oat_xxxxx",
      "expiresAt": null
    },
    "voter": {
      "voterId": "236165",
      "displayName": "Voter 1",
      "status": "active",
      "hasVoted": false
    },
    "ballots": [
      {
        "categoryId": "70f74ae6-fb73-41e9-87e0-ece419585ed5",
        "categoryPosition": 1,
        "title": "PRESIDENT",
        "description": null,
        "instructions": null,
        "candidates": [
          {
            "name": "Kofi Angel",
            "image": "https://cdn-f.crownvote.com/elections/...",
            "bio": null,
            "manifesto": null,
            "vote": "encrypted_vote_value"
          }
        ]
      }
    ]
  }
}

Add loginVoter() to index.js.

This function validates the login form, sends the voter credentials, stores the returned access token, keeps the ballots in state, and prepares the ballot screen.

code Add to index.js
async function loginVoter(event) {
  event.preventDefault()
  hideNotice()

  const voterId = elements.voterId.value.trim()
  const password = elements.password.value.trim()

  if (!voterId || !password) {
    showNotice('Enter your voter ID and password.')
    return
  }

  const response = await fetch(`${API_URL}/voter/login`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-PUBLIC-KEY': PUBLIC_KEY,
    },
    body: JSON.stringify({
      voterId,
      password,
    }),
  })

  const result = await response.json()

  if (!result.status) {
    showNotice(result.message || 'Voter login failed.')
    return
  }

  state.accessToken = result.data.accessToken.value
  state.ballots = result.data.ballots
  state.selections = {}

  sessionStorage.setItem('crownvote_voter_token', state.accessToken)

  elements.loginForm.classList.add('hidden')
  elements.ballotScreen.classList.remove('hidden')

  elements.voterInfo.textContent = `Voting as ${result.data.voter.displayName}`

  renderBallots()
}

elements.loginForm.addEventListener('submit', loginVoter)

Your index.js should now look like this.

We added a temporary empty renderBallots() function so the login step does not break. The next step fills it in.

code index.js
const API_URL = 'https://api.crownvote.com/api/v1'
const PUBLIC_KEY = 'cv_pub_xxxxx'

const state = {
  election: null,
  accessToken: null,
  ballots: [],
  selections: {},
}

const elements = {
  title: document.querySelector('#electionTitle'),
  description: document.querySelector('#electionDescription'),
  notice: document.querySelector('#notice'),
  loginForm: document.querySelector('#loginForm'),
  voterId: document.querySelector('#voterId'),
  password: document.querySelector('#password'),
  ballotScreen: document.querySelector('#ballotScreen'),
  voterInfo: document.querySelector('#voterInfo'),
  ballots: document.querySelector('#ballots'),
  submitVote: document.querySelector('#submitVote'),
}

function showNotice(message) {
  elements.notice.textContent = message
  elements.notice.classList.remove('hidden')
}

function hideNotice() {
  elements.notice.textContent = ''
  elements.notice.classList.add('hidden')
}

async function getElection() {
  hideNotice()

  const response = await fetch(`${API_URL}/election`, {
    headers: {
      'X-PUBLIC-KEY': PUBLIC_KEY,
    },
  })

  const result = await response.json()

  if (!result.status) {
    showNotice(result.message || 'Unable to load election.')
    return
  }

  state.election = result.data.election

  elements.title.textContent = state.election.title
  elements.description.textContent =
    state.election.description || state.election.instructions || 'Login to cast your vote.'

  if (state.election.votingMode !== 'remote') {
    showNotice('This starter guide is for remote voting only.')
    return
  }

  if (state.election.accessMode !== 'api') {
    showNotice('This election is not configured for API voting.')
    return
  }

  if (state.election.status !== 'live') {
    showNotice('This election is not currently live.')
    return
  }

  elements.loginForm.classList.remove('hidden')
}

async function loginVoter(event) {
  event.preventDefault()
  hideNotice()

  const voterId = elements.voterId.value.trim()
  const password = elements.password.value.trim()

  if (!voterId || !password) {
    showNotice('Enter your voter ID and password.')
    return
  }

  const response = await fetch(`${API_URL}/voter/login`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-PUBLIC-KEY': PUBLIC_KEY,
    },
    body: JSON.stringify({
      voterId,
      password,
    }),
  })

  const result = await response.json()

  if (!result.status) {
    showNotice(result.message || 'Voter login failed.')
    return
  }

  state.accessToken = result.data.accessToken.value
  state.ballots = result.data.ballots
  state.selections = {}

  sessionStorage.setItem('crownvote_voter_token', state.accessToken)

  elements.loginForm.classList.add('hidden')
  elements.ballotScreen.classList.remove('hidden')

  elements.voterInfo.textContent = `Voting as ${result.data.voter.displayName}`

  renderBallots()
}

function renderBallots() {
  // We will build this in the next step.
}

elements.loginForm.addEventListener('submit', loginVoter)

getElection()
login

What you have now

Your app can now authenticate a remote voter and store the returned ballot payload. Next, you will render the ballot categories and candidates.