Problem Statement
Today's certificate generation process creates PDFs dynamically each time a learner clicks "Download Certificate." While the certificate number and completion date are stored at the point of completion, all other certificate fields (learner name, license/bar number, email, course title, credit hours, etc.) are pulled from live account and course data at the moment of download.
This means certificates are not a fixed record of what existed when the learner completed the course.
As a result, a learner could:
Complete a course and download their certificate.
Change their name in an upstream identity system (or via API routes where protected fields are not enforced).
Download the certificate again.
Receive a second certificate with a different name but the same certificate number, completion date, and official template.
The system does not currently retain a record of what was actually printed on a certificate at the point of issuance, making it impossible to verify whether a certificate presented later matches the original data.
Business Impact
This creates a compliance and accreditation risk because certificates are intended to prove that a specific individual completed a specific course on a specific date.
Current risks include:
Credit fraud: One individual completes the course while another individual presents a certificate generated from the same completion record.
Verification limitations: BARBRI cannot conclusively verify what information appeared on a certificate when originally issued because the certificate contents are not retained.
Regulatory exposure: State bars, NASBA, or corporate compliance teams could question certificate integrity if discrepancies are discovered.
Loss of trust: The platform currently guarantees the course and completion date, but not the identity of the learner displayed on the certificate.
No evidence exists that this has been exploited. This is a preventative security and compliance improvement.
Current Behaviour
Certificate PDF generated on demand.
Completion date stored.
Certificate ID stored.
Learner/course metadata retrieved live when downloading.
Re-download may produce different certificate contents if underlying data changes.
API layer does not fully enforce the same restrictions as the user interface regarding name changes.
Proposed Solution
  1. Store Certificate Snapshot at Completion (Required)
When a learner earns a certificate, store all certificate data as an immutable snapshot, including:
Learner name
Email address
License/bar number
Course title
Course metadata
Credit hours
Completion date
Certificate ID
Future downloads should always recreate the certificate from the stored snapshot, ensuring the certificate remains identical regardless of later account changes.
  1. Support Certificate Reissuance Process
Introduce an administrative workflow allowing support teams to reissue certificates when legitimate circumstances occur, such as:
Marriage
Legal name change
Data correction
This ensures certificate history remains auditable while providing a path for valid updates.
  1. Add Tamper-Evident Certificate Seal
Add a validation hash/signature generated from certificate contents and printed on the certificate.
Benefits:
Detects alterations to certificate content.
Enables meaningful verification.
Prevents modification of printed details without detection.
  1. Review Existing Certificates
Decision required:
Freeze historical certificates using current values.
Leave historical certificates operating under the current dynamic model.
  1. Review Certificate Verification Feature
Current findings:
Verification endpoint exists.
Accessible publicly without login.
No visible UI in the product.
Does not verify learner identity.
Cannot detect name-substitution scenarios.
Recommendation: Remove the unused endpoint unless there is a confirmed regulatory or customer requirement.
If retained, replace with a verification flow that validates:
Certificate ID
Learner name
and returns a match / no-match result without exposing personal information.
  1. Enforce Name Restrictions in API
Update API validation to prevent self-service name changes through backend endpoints where the UI already blocks this action.
This is a small, low-risk improvement that reduces the attack surface immediately.