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.
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"
}{
"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.
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.
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()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.