...
The goal of this project is to replace the current vendor for identity verification (IDV) on edx.org, Software Secure, with a new vendor, Persona. On edx.org, IDV is used solely for verifying certain name changes requested by certificate-bearing learners via the Name Affirmation feature in the frontend-app-account MFE, but IDV may be used for other purposes in the Open edX community (e.g. to block generation of a verified certificate).
We propose leveraging the various extensibility mechanisms that the platform supports, but we need guidance around how “pluggable” the solution must be and what IDV-related community needs must be met, particularly since 2U uses IDV for a smaller set of features than the platform currently supports.
Problem
The backend implementation of IDV is in the edx-platform verify_student Django application, and the frontend implementation of IDV and the Name Affirmation feature that uses IDV is in the frontend-app-learning MFE and frontend-app-account MFE, respectively. All three repositories The backend implementation of Name Affirmation is in the edx-name-affirmation plugin. The first three repositories are in the openedx
GitHub organization and are a part of the Open edX platform.
We propose an integration approach that balances the needs of 2U and the community. We propose leveraging the various extensibility mechanisms that the platform supports, but we need guidance around how “pluggable” the solution must be and what IDV-related community needs must be met, particularly since 2U uses IDV for a smaller set of features than the platform currently supports.
There are also parts of the verify_student application that have an unclear purpose and may have limited need in the community save for an operator or two. For example, the ReverifyView does not appear to be used anywhere. We do not have the bandwidth to do deprecation at this time, but it would be helpful to know which parts of the codebase are core and which are unused legacy code.
Use Cases
edx-name-affirmation is in the edx
GitHub organization but it is installed into the platform.
The problem that we are solving is adding the necessary extensibility mechanism to the platform to make IDV generic such that additional IDV implementations can be added to the platform seamlessly and without disruption to the core.
Use Cases
As an Open edX operator or developer, I do not want to have to navigate through vendor or implementation specific IDV code in the core.
As an Open edX operator or developer, I need to be able to integrate an IDV implementation into the platform with no disruption to other Open edX instances or the core, because I need to verify learners' identities. I need to be able to integrate whichever IDV implementation that suits my needs.
As a learner who has earned a certificate, I need to be able to verify my identity after requesting a name change, because this validates that I am who I say I am. This maintains the value of a certificate and ensures that the certificate is attributable to me.
As a learner who has purchased a verifiable course mode, I need to be able to verify my identity so that I can receive a “verified certificate” when I meet the requirements for earning one.
As an Open edX installation operator, I need to be able to integrate an IDV implementation into the platform with no disruption to other Open edX instances, because I need to verify learners' identities so that they can earn verified certificates. I need to be able to integrate whichever IDV implementation that suits my needs.
Proposed Solution
Our proposed solution involves three categories of changes.
Backend Extensibility Mechanisms
...
Proposed Solution
Our proposed solution involves three categories of changes.
Backend Extensibility Mechanisms
Generic Model
In order for the platform to continue to function with respect to the edx-platform verify_student Django application .
Filter Hooks
We propose integrating filter hooks into the IDVerificationService. The IDVerificationService serves as an interface to the verify_student Django application and is used throughout the platform. Callers of this service primarily use it to read data from the IDV related models and to get the URL to the IDV flow.
We propose the following two filters, which will enable full integration of the IDVerificationService with additional IDV implementations.
IDVerificationAttemptsRequested(verifications, uuids, users, statuses)
This filter hook will be used to read IDV records from plugins. This will allow Open edX operators to expose supplemental IDV records to the platform via the IDVerificationService.
The uuids
, users
, and statuses
parameters are intended to be iterables of values to filter the IDV records by, corresponding to the uuid
, user.id
and status
fields of the IDVerificationAttempt model, which is the abstract base class that SoftwareSecurePhotoVerification (via PhotoVerification), SSOVerification, and ManualVerification inherit from. As a note uuid
is not currently a field on the IDVerificationAttempt model; our proposal for its addition is discussed in Refactoring.
verifications
in intended to be an iterable of data class instances that represent IDV records. We propose replacing the use of model instances in the IDVerificationService with data classes. Using data classes will avoid exposing data models outside the verify_student Django application, as encouraged by OEP-49: Django App Patterns.
An app’s Django models and other internal data structures should not be exposed via its Python APIs (unless performance requires it).
Using data classes will also simplify the integration of additional IDV implementations by decoupling the verify_student data model from the data model of additional IDV implementations. Because plugins cannot and likely should not import the IDVerificationAttempt model to use as a parent class, if we continue to use model instances, any additional IDV models must replicate the database schema used by the IDVerificationAttempt model.
The IDVerificationService uses some additional database level filters (e.g. created_at__gt) that would not be supported by this filter. To avoid needing to support additional filter parameters, we propose only supporting querying by a select few fields and doing any additional querying in Python code. This will require some additional lightweight refactoring.
IDVerificationURLRequested(url)
This filter hook will be used to fetch the URL to the IDV flow. This will allow operators to override the URL to the IDV flow.
Event Hooks
We propose introducing event hooks related to the IDV attempt lifecycle. Taking the EXAM_ATTEMPT_*
Open edX events as a model, we propose introducing one Open edX event per IDV status.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_READY
IDV_ATTEMPT_SUBMITTED
IDV_ATTEMPT_MUST_RETRY
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
It’s worth noting that the statuses of the IDVerificationAttempt model may not be entirely generic. The statuses are somewhat specific to Software Secure. As an example, they make the assumption that IDV is an asynchronous process (e.g. the distinction between ready
and submitted
). However, Persona, for example, performs IDV verifications within 5 seconds, so there is no valuable distinction between a ready
and submitted
state. These may not be the best statuses to use, but they map to the existing database states.
We could limit the set of events to more widely applicable ones.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_SUBMITTED
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
The actual shape of the event data could be determined later.
Frontend Extensibility Mechanisms
We propose using the frontend-plugin-framework to integrate additional IDV implementations into the frontend-app-account MFE.
The current IDV flow is hosted in the frontend-app-account MFE. Although it works well, it is incompatible with Persona’s IDV flow. The Persona application collects all PII, government ID photos, and selfie photos (i.e. portrait photos) directly in the application, but edX’s current IDV flow performs all of those functions and submits the collected photos to Software Secure via the LMS.
For this reason, we need to replace the current IDV flow. In order to do this without modifying the current IDV flow hosted in the frontend-app-account MFE, we propose the addition of a PluginSlot from the frontend-plugin-framework to the frontend-app-account MFE. This PluginSlot would wrap the use of the IdVerificationPage in the id-verification
Route
. This would allow us to replace the entire IDV component with a new Persona integration component without needing to modify the route or the router.
There are a few other options for where to add a PluginSlot. These are documented in Alternative PluginSlot Locations.
Sample code is shown below for illustrative purposes.
Code Block |
---|
const IdVerificationPageSlot = () => (
<PluginSlot
id="id_verification_slot"
pluginProps={{
courseId,
}}
/>
<IdVerificationPage />
</PluginSlot>
)
...
subscribe(APP_READY, () => {
ReactDOM.render(
...
<Route path="/notifications/:courseId" element={<NotificationPreferences />} />
<Route path="/notifications" element={<NotificationCourses />} />
<Route path="/id-verification/*" element={<IdVerificationPageSlot />} />
<Route path="/" element={<AccountSettingsPage />} />
<Route path="/notfound" element={<NotFoundPage />} />
...
);
}); |
Refactoring
2U uses IDV on edx.org for the Name Affirmation feature, which verifies certain name changes requested by certificate-bearing learners using IDV. This is because a name change has the potential to change the name displayed on their certificate(s). On the backend, this is implemented by the edx-name-affirmation plugin, and, on the frontend, this is implemented by the NameChange component in the frontend-app-account MFE.
The VerifiedName model has a verification_attempt_id field, which currently stores the id
field of the corresponding SoftwareSecurePhotoVerification instance used to verify a particular name. This field poses a problem for the introduction of an additional IDV implementation because the value of this field on any given row could also refer to the id
field of a Persona IDV record. It would not be possible to determine whether a given id
value refers to a SoftwareSecurePhotoVerification model instance or a Persona IDV record model instance.
We propose amending the IDVerificationAttempt model to add a uuid
field, which would be unique across all IDV-related model instances. 2U would also add a uuid
field to the Persona IDV record model. This uuid
would become the ID that the edX platform uses to uniquely refer to IDV records. Some views and APIs would need to be updated as a result of this changewhile also enabling integration with other implementations of IDV, the platform needs a way to store generic, implementation-agnostic information about IDV attempts.
We will introduce a new model VerificationAttempt
to store this data. This model will hook into the IDVerificationService to expose these attempts across the platform.
The model will likely contain the following fields.
created
modified
user
name
status
expiration_datetime
The status
field will have four options - created
, pending
, approved
, and denied
. These statuses correspond to implementation-agnostic statuses that are sensible for use in the core.
Hooks
We propose using the hooks extension framework to integrate additional IDV implementations into the edx-platform verify_student Django application.
Filter Hooks
We propose integrating a filter hook into the IDVerificationService. The IDVerificationService serves as an interface to the verify_student Django application and is used throughout the platform. Callers of this service primarily use it to read data from the IDV related models and to get the URL to the IDV flow.
We propose the following filter, which will enable full integration of the IDVerificationService with additional IDV implementations.
url = IDVerificationURLRequested(url)
This filter hook will be used to fetch the URL to the IDV flow. This will allow operators to override the URL to the IDV flow.
Event Hooks
We propose introducing event hooks related to the IDV attempt lifecycle. Taking the EXAM_ATTEMPT_*
Open edX events as a model, we propose introducing one Open edX event per status of the generic VerificationAttempt
model.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_PENDING
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
The actual shape of the event data could be determined later.
Frontend Extensibility Mechanisms
We propose using the frontend-plugin-framework to integrate additional IDV implementations into the frontend-app-account MFE.
The current IDV flow is hosted in the frontend-app-account MFE. Although it works well, it is incompatible with Persona’s IDV flow. The Persona application collects all PII, government ID photos, and selfie photos (i.e. portrait photos) directly in the application, but edX’s current IDV flow performs all of those functions and submits the collected photos to Software Secure via the LMS.
For this reason, we need to replace the current IDV flow. In order to do this without modifying the current IDV flow hosted in the frontend-app-account MFE, we propose the addition of a PluginSlot from the frontend-plugin-framework to the frontend-app-account MFE. This PluginSlot would wrap the use of the IdVerificationPage in the id-verification
Route
. This would allow us to replace the entire IDV component with a new Persona integration component without needing to modify the route or the router.
There are a few other options for where to add a PluginSlot. These are documented in Alternative PluginSlot Locations.
Sample code is shown below for illustrative purposes.
Code Block |
---|
const IdVerificationPageSlot = () => (
<PluginSlot
id="id_verification_slot"
pluginProps={{
courseId,
}}
/>
<IdVerificationPage />
</PluginSlot>
)
...
subscribe(APP_READY, () => {
ReactDOM.render(
...
<Route path="/notifications/:courseId" element={<NotificationPreferences />} />
<Route path="/notifications" element={<NotificationCourses />} />
<Route path="/id-verification/*" element={<IdVerificationPageSlot />} />
<Route path="/" element={<AccountSettingsPage />} />
<Route path="/notfound" element={<NotFoundPage />} />
...
);
}); |
Refactoring
2U uses IDV on edx.org for the Name Affirmation feature, which verifies certain name changes requested by certificate-bearing learners using IDV. This is because a name change has the potential to change the name displayed on their certificate(s). On the backend, this is implemented by the edx-name-affirmation plugin, and, on the frontend, this is implemented by the NameChange component in the frontend-app-account MFE.
IDV Attempt References
The VerifiedName model has a verification_attempt_id field, which currently stores the id
field of the corresponding SoftwareSecurePhotoVerification instance used to verify a particular name. This field poses a problem for the introduction of an additional IDV implementation because the value of this field on any given row could also refer to the id
field of a Persona IDV record. It would not be possible to determine whether a given id
value refers to a SoftwareSecurePhotoVerification model instance or a Persona IDV record model instance.
We propose amending the IDVerificationAttempt model to add a verification_attempt
field, which will be a ForeignKey
field to the generic VerificationAttempt
model described above.
Support Tools Verified Name Panel
The frontend-app-support-tools contains a VerifiedName panel for displaying a learner’s verified name and the history of previous verified names. In the modal that opens when viewing the history, each VerifiedName is displayed in a table, and the verification_attempt_id field described above is shown in the “IDV Attempt ID” column.
When hovering over an ID in the column, the status of the corresponding IDV attempt (i.e. of the SoftwareSecurePhotoVerification
modal instance) is shown. The status is retrieved from the IDVerificationSupportView view of the user_api Django application. This view uses the get_verification_details_by_id method of the IDVerificationService, which queries the SoftwareSecurePhotoVerification
, SSOVerification
, and ManualVerification
models by their id
. This works due to inheritance from the abstract base class IDVerificationAttempt
.
With the introduction of the VerificationAttempt
model, an id
cannot uniquely identify a instance of the VerificationAttempt
, SoftwareSecurePhotoVerification
, SSOVerification
, and ManualVerification
models. This means that the get_verification_details_by_id method of the IDVerificationService will no longer work, breaking the Support Tools.
As a solution, the VerifiedName panel will be refactored to pull the IDV attempt status from the edx-name-affirmation application. The edx-name-affirmation Django application will refactor its VerifiedNameHistoryView view to pull this data by querying the model associated with either the verification_attempt_id
or verification_attempt
fields. As a result, the use of the VerifiedNameHistoryView view and the get_verification_details_by_id method of the IDVerificationService can be removed.
Name Change Signal Handler
Currently, the edx-name-affirmation Django application associates VerifiedNames with IDV attempts via the verification_attempt_id field. This association is formed by a task that is triggered by a signal handler that listens for a signal emitted by the SoftwareSecurePhotoVerification model. This means that only Software Secure IDV attempts can change the status of VerifiedNames (e.g. approve or deny them).
We propose triggering this task on the event hooks listed above. The impact of this change is that any form of IDV can change the status of VerifiedNames (e.g. approve or deny them). For example, an instance of the ManualVerification
class can be used to approve or deny a VerifiedName.
To handle deletes, the idv_delete_handler will be connected to the VerificationAttempt
post_delete
signal as it is for the SoftwareSecurePhotoVerification model.
Name Change Modal
We propose modifying the copy in the name change modal to make the instructions more generic. Some IDV implementations do not support non-government IDs.
We propose the following text.
Enter your name as it appears on your identification card.
Other Approaches Considered
...