diff --git a/common/djangoapps/student/tests/test_verification_status.py b/common/djangoapps/student/tests/test_verification_status.py
index 08093f24486d8052c4c46ebf27ac85c23841b9f3..f320be7aa23dd53452c5ea888a09ad2ab15715c6 100644
--- a/common/djangoapps/student/tests/test_verification_status.py
+++ b/common/djangoapps/student/tests/test_verification_status.py
@@ -371,8 +371,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
         # Verify that the correct banner color is rendered
         self.assertContains(
             response,
-            "<div class=\"course {}\" aria-labelledby=\"course-title-{}\">".format(
-                self.MODE_CLASSES[status], self.course.id)
+            "<article class=\"course {}\"".format(self.MODE_CLASSES[status])
         )
 
         # Verify that the correct copy is rendered on the dashboard
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index a44664feda2074d0098be3a38f5bef5e53e64253..804200fa2364b0fe6c1fa432569223c09ca7a9ae 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -58,6 +58,7 @@ from bulk_email.models import BulkEmailFlag, Optout  # pylint: disable=import-er
 from certificates.api import get_certificate_url, has_html_certificates_enabled  # pylint: disable=import-error
 from certificates.models import (  # pylint: disable=import-error
     CertificateStatuses,
+    GeneratedCertificate,
     certificate_status_for_student
 )
 from course_modes.models import CourseMode
@@ -65,6 +66,7 @@ from courseware.access import has_access
 from courseware.courses import get_courses, sort_by_announcement, sort_by_start_date  # pylint: disable=import-error
 from django_comment_common.models import assign_role
 from edxmako.shortcuts import render_to_response, render_to_string
+from entitlements.models import CourseEntitlement
 from eventtracking import tracker
 from lms.djangoapps.commerce.utils import EcommerceService  # pylint: disable=import-error
 from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
@@ -72,7 +74,7 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
 # Note that this lives in LMS, so this dependency should be refactored.
 from notification_prefs.views import enable_notifications
 from openedx.core.djangoapps import monitoring_utils
-from openedx.core.djangoapps.catalog.utils import get_programs_with_type
+from openedx.core.djangoapps.catalog.utils import get_programs_with_type, get_course_runs_for_course
 from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course
 from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names, make_providers_strings
 from openedx.core.djangoapps.embargo import api as embargo_api
@@ -686,15 +688,22 @@ def dashboard(request):
         'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
     ) or settings.SUPPORT_SITE_LINK
 
-    # get the org whitelist or the org blacklist for the current site
+    # Get the org whitelist or the org blacklist for the current site
     site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site(user)
     course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist))
 
+    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
+    course_entitlements = list(CourseEntitlement.objects.filter(user=user).select_related('enrollment_course_run'))
+    course_entitlement_available_sessions = {
+        str(entitlement.uuid): get_course_runs_for_course(str(entitlement.course_uuid))
+        for entitlement in course_entitlements
+    }
+
     # Record how many courses there are so that we can get a better
     # understanding of usage patterns on prod.
     monitoring_utils.accumulate('num_courses', len(course_enrollments))
 
-    # sort the enrollment pairs by the enrollment date
+    # Sort the enrollment pairs by the enrollment date
     course_enrollments.sort(key=lambda x: x.created, reverse=True)
 
     # Retrieve the course modes for each course
@@ -863,6 +872,10 @@ def dashboard(request):
     valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired']
     display_sidebar_on_dashboard = len(order_history_list) or verification_status in valid_verification_statuses
 
+    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
+    for entitlement in [e for e in course_entitlements if e.enrollment_course_run is not None]:
+        course_enrollments = [enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id]  # pylint: disable=line-too-long
+
     context = {
         'enterprise_message': enterprise_message,
         'consent_required_courses': consent_required_courses,
@@ -871,6 +884,8 @@ def dashboard(request):
         'redirect_message': redirect_message,
         'account_activation_messages': account_activation_messages,
         'course_enrollments': course_enrollments,
+        'course_entitlements': course_entitlements,
+        'course_entitlement_available_sessions': course_entitlement_available_sessions,
         'course_optouts': course_optouts,
         'banner_account_activation_message': banner_account_activation_message,
         'sidebar_account_activation_message': sidebar_account_activation_message,
diff --git a/lms/static/js/dashboard/dropdown.js b/lms/static/js/dashboard/dropdown.js
index d05b90d7af2baab811a17ee126f9987cdcac8ea2..dc67c44b84abb1ecb687975105397edec48cca0f 100644
--- a/lms/static/js/dashboard/dropdown.js
+++ b/lms/static/js/dashboard/dropdown.js
@@ -10,10 +10,10 @@ var edx = edx || {};
     edx.dashboard.dropdown.toggleCourseActionsDropdownMenu = function(event) {
         // define variables for code legibility
         var dashboardIndex = $(event.currentTarget).data().dashboardIndex,
-            dropdown = $('#actions-dropdown-' + dashboardIndex),
+            $dropdown = $('#actions-dropdown-' + dashboardIndex),
             dropdownButton = $('#actions-dropdown-link-' + dashboardIndex),
             ariaExpandedState = (dropdownButton.attr('aria-expanded') === 'true'),
-            menuItems = dropdown.find('a');
+            menuItems = $dropdown.find('a');
 
         var catchKeyPress = function(object, event) {
             // get currently focused item
@@ -57,12 +57,12 @@ var edx = edx || {};
         };
 
         // Toggle the visibility control for the selected element and set the focus
-        dropdown.toggleClass('is-visible');
-        if (dropdown.hasClass('is-visible')) {
-            dropdown.attr('tabindex', -1);
-            dropdown.focus();
+        $dropdown.toggleClass('is-visible');
+        if ($dropdown.hasClass('is-visible')) {
+            $dropdown.attr('tabindex', -1);
+            $dropdown.focus();
         } else {
-            dropdown.removeAttr('tabindex');
+            $dropdown.removeAttr('tabindex');
             dropdownButton.focus();
         }
 
@@ -71,8 +71,8 @@ var edx = edx || {};
 
         // catch keypresses when inside dropdownMenu (we want to catch spacebar;
         // escape; up arrow or shift+tab; and down arrow or tab)
-        dropdown.on('keydown', function(event) {
-            catchKeyPress($(this), event);
+        $dropdown.on('keydown', function(e) {
+            catchKeyPress($(this), e);
         });
     };
 
diff --git a/lms/static/js/learner_dashboard/course_entitlement_factory.js b/lms/static/js/learner_dashboard/course_entitlement_factory.js
new file mode 100644
index 0000000000000000000000000000000000000000..50b18841d23cd613c2a346ab349da29b9faba699
--- /dev/null
+++ b/lms/static/js/learner_dashboard/course_entitlement_factory.js
@@ -0,0 +1,12 @@
+(function(define) {
+    'use strict';
+
+    define([
+        'js/learner_dashboard/views/course_entitlement_view'
+    ],
+    function(EntitlementView) {
+        return function(options) {
+            return new EntitlementView(options);
+        };
+    });
+}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/learner_dashboard/models/course_card_model.js b/lms/static/js/learner_dashboard/models/course_card_model.js
index d4cf89911ca32363c2118587c828b9aea4db26d5..5d86806b83072ebe0b662f04e8768286ca1448fa 100644
--- a/lms/static/js/learner_dashboard/models/course_card_model.js
+++ b/lms/static/js/learner_dashboard/models/course_card_model.js
@@ -6,10 +6,12 @@
     define([
         'backbone',
         'underscore',
+        'gettext',
         'jquery',
-        'edx-ui-toolkit/js/utils/date-utils'
+        'edx-ui-toolkit/js/utils/date-utils',
+        'edx-ui-toolkit/js/utils/string-utils'
     ],
-        function(Backbone, _, $, DateUtils) {
+        function(Backbone, _, gettext, $, DateUtils, StringUtils) {
             return Backbone.Model.extend({
                 initialize: function(data) {
                     if (data) {
@@ -140,7 +142,7 @@
 
                 formatDateString: function(run) {
                     var pacingType = run.pacing_type,
-                        dateString = '',
+                        dateString,
                         start = this.get('start_date') || run.start_date,
                         end = this.get('end_date') || run.end_date,
                         now = new Date(),
@@ -148,21 +150,24 @@
                         endDate = new Date(end);
 
                     if (pacingType === 'self_paced') {
-                        dateString = 'Self-paced';
-                        if (start && startDate > now) {
-                            dateString += ' - Starts ' + start;
+                        if (start) {
+                            dateString = startDate > now ?
+                                StringUtils.interpolate(gettext('(Self-paced) Starts {start}'), {start: start}) :
+                                StringUtils.interpolate(gettext('(Self-paced) Started {start}'), {start: start});
                         } else if (end && endDate > now) {
-                            dateString += ' - Ends ' + end;
+                            dateString = StringUtils.interpolate(gettext('(Self-paced) Ends {end}'), {end: end});
                         } else if (end && endDate < now) {
-                            dateString += ' - Ended ' + end;
+                            dateString = StringUtils.interpolate(gettext('(Self-paced) Ended {end}'), {end: end});
                         }
                     } else {
                         if (start && end) {
                             dateString = start + ' - ' + end;
                         } else if (start) {
-                            dateString = 'Starts ' + start;
+                            dateString = startDate > now ?
+                                StringUtils.interpolate(gettext('Starts {start}'), {start: start}) :
+                                StringUtils.interpolate(gettext('Started {start}'), {start: start});
                         } else if (end) {
-                            dateString = 'Ends ' + end;
+                            dateString = StringUtils.interpolate(gettext('Ends {end}'), {end: end});
                         }
                     }
                     return dateString;
diff --git a/lms/static/js/learner_dashboard/models/course_entitlement_model.js b/lms/static/js/learner_dashboard/models/course_entitlement_model.js
new file mode 100644
index 0000000000000000000000000000000000000000..ebd666822dd18cef31f554415e1922d77d19373c
--- /dev/null
+++ b/lms/static/js/learner_dashboard/models/course_entitlement_model.js
@@ -0,0 +1,22 @@
+/**
+ *  Store data for the current
+ */
+(function(define) {
+    'use strict';
+
+    define([
+        'backbone'
+    ],
+        function(Backbone) {
+            return Backbone.Model.extend({
+                defaults: {
+                    availableSessions: [],
+                    entitlementUUID: '',
+                    currentSessionId: '',
+                    userId: '',
+                    courseName: ''
+                }
+            });
+        }
+    );
+}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/learner_dashboard/views/course_entitlement_view.js b/lms/static/js/learner_dashboard/views/course_entitlement_view.js
new file mode 100644
index 0000000000000000000000000000000000000000..cf974973b956415a2d5bb20b9fa7e0d1d123f86e
--- /dev/null
+++ b/lms/static/js/learner_dashboard/views/course_entitlement_view.js
@@ -0,0 +1,399 @@
+// This is required for karma testing due to a known issue in Bootstrap-v4: https://github.com/twbs/bootstrap/pull/22888
+// The issue is that bootstrap tries to access Popper's global Popper object which is not initialized on loading
+// from the karma configuration. The next version of bootstrap (>v4.2) will solve this issue.
+// Once this is resolved, we should import bootstrap through require-config.js and main.js (for jasmine testing)
+var defineFn = require || RequireJS.require;  // eslint-disable-line global-require
+var Popper = defineFn(['common/js/vendor/popper']);
+defineFn(['common/js/vendor/bootstrap']);
+
+(function(define) {
+    'use strict';
+
+    define(['backbone',
+        'jquery',
+        'underscore',
+        'gettext',
+        'moment',
+        'edx-ui-toolkit/js/utils/html-utils',
+        'js/learner_dashboard/models/course_entitlement_model',
+        'js/learner_dashboard/models/course_card_model',
+        'text!../../../templates/learner_dashboard/course_entitlement.underscore',
+        'text!../../../templates/learner_dashboard/verification_popover.underscore'
+    ],
+         function(
+             Backbone,
+             $,
+             _,
+             gettext,
+             moment,
+             HtmlUtils,
+             EntitlementModel,
+             CourseCardModel,
+             pageTpl,
+             verificationPopoverTpl
+         ) {
+             return Backbone.View.extend({
+                 tpl: HtmlUtils.template(pageTpl),
+                 verificationTpl: HtmlUtils.template(verificationPopoverTpl),
+
+                 events: {
+                     'change .session-select': 'updateEnrollBtn',
+                     'click .enroll-btn': 'handleEnrollChange',
+                     'keydown .final-confirmation-btn': 'handleVerificationPopoverA11y',
+                     'click .popover-dismiss': 'hideDialog'
+                 },
+
+                 initialize: function(options) {
+                     // Set up models and reload view on change
+                     this.courseCardModel = new CourseCardModel();
+                     this.entitlementModel = new EntitlementModel({
+                         availableSessions: this.formatDates(JSON.parse(options.availableSessions)),
+                         entitlementUUID: options.entitlementUUID,
+                         currentSessionId: options.currentSessionId,
+                         userId: options.userId,
+                         courseName: options.courseName
+                     });
+                     this.listenTo(this.entitlementModel, 'change', this.render);
+
+                     // Grab URLs that handle changing of enrollment and entering a newly selected session.
+                     this.enrollUrl = options.enrollUrl;
+                     this.courseHomeUrl = options.courseHomeUrl;
+
+                     // Grab elements from the parent card that work with this view and bind associated events
+                     this.$triggerOpenBtn = $(options.triggerOpenBtn); // Opens/closes session selection view
+                     this.$dateDisplayField = $(options.dateDisplayField); // Displays current session dates
+                     this.$enterCourseBtn = $(options.enterCourseBtn); // Button link to course home page
+                     this.$courseCardMessages = $(options.courseCardMessages); // Additional session messages
+                     this.$courseTitleLink = $(options.courseTitleLink); // Title link to course home page
+                     this.$courseImageLink = $(options.courseImageLink); // Image link to course home page
+                     this.$triggerOpenBtn.on('click', this.toggleSessionSelectionPanel.bind(this));
+
+                     this.render(options);
+                     this.postRender();
+                 },
+
+                 render: function() {
+                     HtmlUtils.setHtml(this.$el, this.tpl(this.entitlementModel.toJSON()));
+                     this.delegateEvents();
+                     this.updateEnrollBtn();
+                     return this;
+                 },
+
+                 postRender: function() {
+                     // Close popover on click-away
+                     $(document).on('click', function(e) {
+                         if (!($(e.target).closest('.enroll-btn-initial, .popover').length)) {
+                             this.hideDialog(this.$('.enroll-btn-initial'));
+                         }
+                     }.bind(this));
+
+                     this.$('.enroll-btn-initial').click(function(e) {
+                         this.showDialog($(e.target));
+                     }.bind(this));
+                 },
+
+                 handleEnrollChange: function() {
+                     /*
+                     Handles enrolling in a course, unenrolling in a session and changing session.
+                     The new session id is stored as a data attribute on the option in the session-select element.
+                     */
+                     var isLeavingSession;
+
+                     // Do not allow for enrollment when button is disabled
+                     if (this.$('.enroll-btn-initial').hasClass('disabled')) return;
+
+                     // Grab the id for the desired session, an leave session event will return null
+                     this.currentSessionSelection = this.$('.session-select')
+                         .find('option:selected').data('session_id');
+                     isLeavingSession = !this.currentSessionSelection;
+
+                     // Display the indicator icon
+                     HtmlUtils.setHtml(this.$dateDisplayField,
+                         HtmlUtils.HTML('<span class="fa fa-spinner fa-spin" aria-hidden="true"></span>')
+                     );
+
+                     $.ajax({
+                         type: isLeavingSession ? 'DELETE' : 'POST',
+                         url: this.enrollUrl,
+                         contentType: 'application/json',
+                         dataType: 'json',
+                         data: JSON.stringify({
+                             course_run_id: this.currentSessionSelection
+                         }),
+                         statusCode: {
+                             201: _.bind(this.enrollSuccess, this),
+                             204: _.bind(this.unenrollSuccess, this)
+                         },
+                         error: _.bind(this.enrollError, this)
+                     });
+                 },
+
+                 enrollSuccess: function(data) {
+                     /*
+                     Update external elements on the course card to represent the now available course session.
+
+                     1) Show the change session toggle button.
+                     2) Add the new session's dates to the date field on the main course card.
+                     3) Hide the 'View Course' button to the course card.
+                     */
+                     var successIconEl = '<span class="fa fa-check" aria-hidden="true"></span>';
+
+                     // Update the model with the new session Id;
+                     this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
+
+                     // Allow user to change session
+                     this.$triggerOpenBtn.removeClass('hidden');
+
+                     // Display a success indicator
+                     HtmlUtils.setHtml(this.$dateDisplayField,
+                         HtmlUtils.joinHtml(
+                             HtmlUtils.HTML(successIconEl),
+                             this.getAvailableSessionWithId(data.course_run_id).session_dates
+                         )
+                     );
+
+                     // Ensure the view course button links to new session home page and place focus there
+                     this.$enterCourseBtn
+                         .attr('href', this.formatCourseHomeUrl(data.course_run_id))
+                         .removeClass('hidden')
+                         .focus();
+                     this.toggleSessionSelectionPanel();
+                 },
+
+                 unenrollSuccess: function() {
+                     /*
+                     Update external elements on the course card to represent the unenrolled state.
+
+                     1) Hide the change session button and the date field.
+                     2) Hide the 'View Course' button.
+                     3) Remove the messages associated with the enrolled state.
+                     4) Remove the link from the course card image and title.
+                     */
+
+                     // Update the model with the new session Id;
+                     this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
+
+                     // Reset the card contents to the unenrolled state
+                     this.$triggerOpenBtn.addClass('hidden');
+                     this.$enterCourseBtn.addClass('hidden');
+                     this.$courseCardMessages.remove();
+                     this.$('.enroll-btn-initial').focus();
+                     HtmlUtils.setHtml(
+                        this.$dateDisplayField,
+                         HtmlUtils.joinHtml(
+                             HtmlUtils.HTML('<span class="icon fa fa-warning" aria-hidden="true"></span>'),
+                             HtmlUtils.HTML(gettext('You must select a session to access the course.'))
+                         )
+                     );
+
+                     // Remove links to previously enrolled sessions
+                     this.$courseImageLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
+                         HtmlUtils.joinHtml(
+                             HtmlUtils.HTML('<div class="'),
+                             this.$courseImageLink.attr('class'),
+                             HtmlUtils.HTML('" tabindex="-1">'),
+                             HtmlUtils.HTML(this.$courseImageLink.html()),
+                             HtmlUtils.HTML('</div>')
+                         ).text
+                     );
+                     this.$courseTitleLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
+                         HtmlUtils.joinHtml(
+                             HtmlUtils.HTML('<span>'),
+                             this.$courseTitleLink.text(),
+                             HtmlUtils.HTML('</span>')
+                         ).text
+                     );
+                 },
+
+                 enrollError: function() {
+                     var errorMsgEl = HtmlUtils.HTML(
+                         gettext('There was an error. Please reload the page and try again.')
+                     ).text;
+                     this.$dateDisplayField
+                         .find('.fa.fa-spin')
+                         .removeClass('fa-spin fa-spinner')
+                         .addClass('fa-close');
+                     this.$dateDisplayField.append(errorMsgEl);
+                     this.hideDialog(this.$('.enroll-btn-initial'));
+                 },
+
+                 updateEnrollBtn: function() {
+                     /*
+                     This function is invoked on load, on opening the view and on changing the option on the session
+                     selection dropdown. It plays three roles:
+                     1) Enables and disables enroll button
+                     2) Changes text to describe the action taken
+                     3) Formats the confirmation popover to allow for two step authentication
+                     */
+                     var enrollText,
+                         currentSessionId = this.entitlementModel.get('currentSessionId'),
+                         newSessionId = this.$('.session-select').find('option:selected').data('session_id'),
+                         enrollBtnInitial = this.$('.enroll-btn-initial');
+
+                     // Disable the button if the user is already enrolled in that session.
+                     if (currentSessionId === newSessionId) {
+                         enrollBtnInitial.addClass('disabled');
+                         this.removeDialog(enrollBtnInitial);
+                         return;
+                     }
+                     enrollBtnInitial.removeClass('disabled');
+
+                     // Update button text specifying if the user is initially enrolling, changing or leaving a session.
+                     if (newSessionId) {
+                         enrollText = currentSessionId ? gettext('Change Session') : gettext('Select Session');
+                     } else {
+                         enrollText = gettext('Leave Current Session');
+                     }
+                     enrollBtnInitial.text(enrollText);
+                     this.removeDialog(enrollBtnInitial);
+                     this.initializeVerificationDialog(enrollBtnInitial);
+                 },
+
+                 toggleSessionSelectionPanel: function() {
+                     /*
+                     Opens and closes the session selection panel.
+                     */
+                     this.$el.toggleClass('hidden');
+                     if (!this.$el.hasClass('hidden')) {
+                         // Set focus to the session selection for a11y purposes
+                         this.$('.session-select').focus();
+                         this.hideDialog(this.$('.enroll-btn-initial'));
+                     }
+                     this.updateEnrollBtn();
+                 },
+
+                 initializeVerificationDialog: function(invokingElement) {
+                     /*
+                     Instantiates an instance of the Bootstrap v4 dialog modal and attaches it to the passed in element.
+
+                     This dialog acts as the second step in verifying the user's action to select, change or leave an
+                     available course session.
+                      */
+                     var confirmationMsgTitle,
+                         confirmationMsgBody,
+                         popoverDialogHtml,
+                         currentSessionId = this.entitlementModel.get('currentSessionId'),
+                         newSessionId = this.$('.session-select').find('option:selected').data('session_id');
+
+                     // Update the button popover text to enable two step authentication.
+                     if (newSessionId) {
+                         confirmationMsgTitle = !currentSessionId ?
+                             gettext('Are you sure you want to select this session?') :
+                             gettext('Are you sure you want to change to a different session?');
+                         confirmationMsgBody = !currentSessionId ? '' :
+                             gettext('Any course progress or grades from your current session will be lost.');
+                     } else {
+                         confirmationMsgTitle = gettext('Are you sure that you want to leave this session?');
+                         confirmationMsgBody = gettext('Any course progress or grades from your current session will be lost.'); // eslint-disable-line max-len
+                     }
+
+                     // Remove existing popover and re-initialize
+                     popoverDialogHtml = this.verificationTpl({
+                         confirmationMsgTitle: confirmationMsgTitle,
+                         confirmationMsgBody: confirmationMsgBody
+                     });
+
+                     invokingElement.popover({
+                         placement: 'bottom',
+                         container: this.$el,
+                         html: true,
+                         trigger: 'click',
+                         content: popoverDialogHtml.text
+                     });
+                 },
+
+                 removeDialog: function(invokingElement) {
+                     /* Removes the Bootstrap v4 dialog modal from the update session enrollment button. */
+                     invokingElement.popover('dispose');
+                 },
+
+                 showDialog: function(invokingElement) {
+                     /* Given an element with an associated dialog modal, shows the modal. */
+                     invokingElement.popover('show');
+                     this.$('.final-confirmation-btn:first').focus();
+                 },
+
+                 hideDialog: function(el, returnFocus) {
+                     /* Hides the  modal without removing it from the DOM. */
+                     var $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
+                     $el.popover('hide');
+                     if (returnFocus) {
+                         $el.focus();
+                     }
+                 },
+
+                 handleVerificationPopoverA11y: function(e) {
+                     /* Ensure that the second step verification popover is treated as an a11y compliant dialog */
+                     var $nextButton,
+                         $verificationOption = $(e.target),
+                         openButton = $(e.target).closest('.course-entitlement-selection-container')
+                             .find('.enroll-btn-initial');
+                     if (e.key === 'Tab') {
+                         e.preventDefault();
+                         $nextButton = $verificationOption.is(':first-child') ?
+                             $verificationOption.next('.final-confirmation-btn') :
+                             $verificationOption.prev('.final-confirmation-btn');
+                         $nextButton.focus();
+                     } else if (e.key === 'Escape') {
+                         this.hideDialog(openButton);
+                         openButton.focus();
+                     }
+                 },
+
+                 formatCourseHomeUrl: function(sessionKey) {
+                     /*
+                     Takes the base course home URL and updates it with the new session id, leveraging the
+                     the fact that all course keys contain a '+' symbol.
+                     */
+                     var oldSessionKey = this.courseHomeUrl.split('/')
+                         .filter(
+                             function(urlParam) {
+                                 return urlParam.indexOf('+') > 0;
+                             }
+                         )[0];
+                     return this.courseHomeUrl.replace(oldSessionKey, sessionKey);
+                 },
+
+                 formatDates: function(sessionData) {
+                     /*
+                     Takes a data object containing the upcoming available sessions for an entitlement and returns
+                     the object with a session_dates attribute representing a formatted date string that highlights
+                     the start and end dates of the particular session.
+                      */
+                     var formattedSessionData = sessionData,
+                         startDate,
+                         endDate,
+                         dateFormat;
+                     // Set the date format string to the user's selected language
+                     moment.locale(document.documentElement.lang);
+                     dateFormat = moment.localeData().longDateFormat('L').indexOf('DD') >
+                         moment.localeData().longDateFormat('L').indexOf('MM') ? 'MMMM D, YYYY' : 'D MMMM, YYYY';
+
+                     return _.map(formattedSessionData, function(session) {
+                         var formattedSession = session;
+                         startDate = this.formatDate(formattedSession.session_start, dateFormat);
+                         endDate = this.formatDate(formattedSession.session_end, dateFormat);
+                         formattedSession.enrollment_end = this.formatDate(formattedSession.enrollment_end, dateFormat);
+                         formattedSession.session_dates = this.courseCardModel.formatDateString({
+                             start_date: session.session_start_advertised || startDate,
+                             end_date: session.session_start_advertised ? null : endDate,
+                             pacing_type: formattedSession.pacing_type
+                         });
+                         return formattedSession;
+                     }, this);
+                 },
+
+                 formatDate: function(date, dateFormat) {
+                     return date ? moment((new Date(date))).format(dateFormat) : null;
+                 },
+
+                 getAvailableSessionWithId: function(sessionId) {
+                     /* Returns an available session given a sessionId */
+                     return this.entitlementModel.get('availableSessions').find(function(session) {
+                         return session.session_id === sessionId;
+                     });
+                 }
+             });
+         }
+    );
+}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js b/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..934613d9292f25f0f28a749b78e0fee9f133e5a4
--- /dev/null
+++ b/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js
@@ -0,0 +1,158 @@
+define([
+    'backbone',
+    'underscore',
+    'jquery',
+    'js/learner_dashboard/models/course_entitlement_model',
+    'js/learner_dashboard/views/course_entitlement_view'
+], function(Backbone, _, $, CourseEntitlementModel, CourseEntitlementView) {
+    'use strict';
+
+    describe('Course Entitlement View', function() {
+        var view = null,
+            setupView,
+            selectOptions,
+            entitlementAvailableSessions,
+            initialSessionId,
+            entitlementUUID = 'a9aiuw76a4ijs43u18',
+            testSessionIds = ['test_session_id_1', 'test_session_id_2'];
+
+        setupView = function(isAlreadyEnrolled) {
+            setFixtures('<div class="course-entitlement-selection-container"></div>');
+
+            initialSessionId = isAlreadyEnrolled ? testSessionIds[0] : '';
+            entitlementAvailableSessions = [{
+                enrollment_end: null,
+                session_start: '2013-02-05T05:00:00+00:00',
+                pacing_type: 'instructor_paced',
+                session_id: testSessionIds[0],
+                session_end: null
+            }, {
+                enrollment_end: '2017-12-22T03:30:00Z',
+                session_start: '2018-01-03T13:00:00+00:00',
+                pacing_type: 'self_paced',
+                session_id: testSessionIds[1],
+                session_end: '2018-03-09T21:30:00+00:00'
+            }];
+
+            view = new CourseEntitlementView({
+                el: '.course-entitlement-selection-container',
+                triggerOpenBtn: '#course-card-0 .change-session',
+                courseCardMessages: '#course-card-0 .messages-list > .message',
+                courseTitleLink: '#course-card-0 .course-title a',
+                courseImageLink: '#course-card-0 .wrapper-course-image > a',
+                dateDisplayField: '#course-card-0 .info-date-block',
+                enterCourseBtn: '#course-card-0 .enter-course',
+                availableSessions: JSON.stringify(entitlementAvailableSessions),
+                entitlementUUID: entitlementUUID,
+                currentSessionId: initialSessionId,
+                userId: '1',
+                enrollUrl: '/api/enrollment/v1/enrollment',
+                courseHomeUrl: '/courses/course-v1:edX+DemoX+Demo_Course/course/'
+            });
+        };
+
+        afterEach(function() {
+            if (view) view.remove();
+        });
+
+        describe('Initialization of view', function() {
+            it('Should create a entitlement view element', function() {
+                setupView(false);
+                expect(view).toBeDefined();
+            });
+        });
+
+        describe('Available Sessions Select - Unfulfilled Entitlement', function() {
+            beforeEach(function() {
+                setupView(false);
+                selectOptions = view.$('.session-select').find('option');
+            });
+
+            it('Select session dropdown should show all available course runs and a coming soon option.', function() {
+                expect(selectOptions.length).toEqual(entitlementAvailableSessions.length + 1);
+            });
+
+            it('Self paced courses should have visual indication in the selection option.', function() {
+                var selfPacedOptionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
+                    return session.pacing_type === 'self_paced';
+                });
+                var selfPacedOption = selectOptions[selfPacedOptionIndex];
+                expect(selfPacedOption && selfPacedOption.text.includes('(Self-paced)')).toBe(true);
+            });
+
+            it('Courses with an an enroll by date should indicate so on the selection option.', function() {
+                var enrollEndSetOptionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
+                    return session.enrollment_end !== null;
+                });
+                var enrollEndSetOption = selectOptions[enrollEndSetOptionIndex];
+                expect(enrollEndSetOption && enrollEndSetOption.text.includes('Open until')).toBe(true);
+            });
+
+            it('Title element should correctly indicate the expected behavior.', function() {
+                expect(view.$('.action-header').text().includes(
+                    'To access the course, select a session.'
+                )).toBe(true);
+            });
+        });
+
+        describe('Available Sessions Select - Fulfilled Entitlement', function() {
+            beforeEach(function() {
+                setupView(true);
+                selectOptions = view.$('.session-select').find('option');
+            });
+
+            it('Select session dropdown should show available course runs, coming soon and leave options.', function() {
+                expect(selectOptions.length).toEqual(entitlementAvailableSessions.length + 2);
+            });
+
+            it('Select session dropdown should allow user to leave the current session.', function() {
+                var leaveSessionOption = selectOptions[selectOptions.length - 1];
+                expect(leaveSessionOption.text.includes('Leave the current session and decide later')).toBe(true);
+            });
+
+            it('Currently selected session should be specified in the dropdown options.', function() {
+                var selectedSessionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
+                    return initialSessionId === session.session_id;
+                });
+                expect(selectOptions[selectedSessionIndex].text.includes('Currently Selected')).toBe(true);
+            });
+
+            it('Title element should correctly indicate the expected behavior.', function() {
+                expect(view.$('.action-header').text().includes(
+                    'Change to a different session or leave the current session.'
+                )).toBe(true);
+            });
+        });
+
+        describe('Select Session Action Button and popover behavior - Unfulfilled Entitlement', function() {
+            beforeEach(function() {
+                setupView(false);
+            });
+
+            it('Change session button should have the correct text.', function() {
+                expect(view.$('.enroll-btn-initial').text() === 'Select Session').toBe(true);
+            });
+
+            it('Select session button should show popover when clicked.', function() {
+                view.$('.enroll-btn-initial').click();
+                expect(view.$('.verification-modal').length > 0).toBe(true);
+            });
+        });
+
+        describe('Change Session Action Button and popover behavior - Fulfilled Entitlement', function() {
+            beforeEach(function() {
+                setupView(true);
+                selectOptions = view.$('.session-select').find('option');
+            });
+
+            it('Change session button should show correct text.', function() {
+                expect(view.$('.enroll-btn-initial').text().trim() === 'Change Session').toBe(true);
+            });
+
+            it('Switch session button should be disabled when on the currently enrolled session.', function() {
+                expect(view.$('.enroll-btn-initial')).toHaveClass('disabled');
+            });
+        });
+    });
+}
+);
diff --git a/lms/static/lms/js/require-config.js b/lms/static/lms/js/require-config.js
index ba785e1d51300c29e1f62de9e6ff309367d6277f..c438c2137c98810a28e7c5dfd582e0cc0fbe8ee8 100644
--- a/lms/static/lms/js/require-config.js
+++ b/lms/static/lms/js/require-config.js
@@ -100,6 +100,8 @@
             'string_utils': 'js/src/string_utils',
             'utility': 'js/src/utility',
             'draggabilly': 'js/vendor/draggabilly',
+            'popper': 'common/js/vendor/popper',
+            'bootstrap': 'common/js/vendor/bootstrap',
 
             // Files needed by OVA
             'annotator': 'js/vendor/ova/annotator-full',
@@ -206,6 +208,13 @@
             'grouping-annotator': {
                 deps: ['annotator']
             },
+            'popper': {
+                exports: 'Popper'
+            },
+            'bootstrap': {
+                deps: ['jquery', 'popper'],
+                exports: 'bootstrap'
+            },
             'ova': {
                 exports: 'ova',
                 deps: [
diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js
index 0ac5f80d62d54ba365e6622f978f9f9dc7e0b36f..f52a2085657b8941e5744e8a9787e0e77cbca912 100644
--- a/lms/static/lms/js/spec/main.js
+++ b/lms/static/lms/js/spec/main.js
@@ -762,6 +762,7 @@
         'js/spec/learner_dashboard/unenroll_view_spec.js',
         'js/spec/learner_dashboard/course_card_view_spec.js',
         'js/spec/learner_dashboard/course_enroll_view_spec.js',
+        'js/spec/learner_dashboard/course_entitlement_view_spec.js',
         'js/spec/markdown_editor_spec.js',
         'js/spec/dateutil_factory_spec.js',
         'js/spec/navigation_spec.js',
diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss
index b6e0b51151058a978125ae43d8c5afd69993a55d..6ee1904ea6feb8bf8124e94cdaf2f7848c9a5ad4 100644
--- a/lms/static/sass/multicourse/_dashboard.scss
+++ b/lms/static/sass/multicourse/_dashboard.scss
@@ -163,11 +163,31 @@
                 display: inline-block;
               }
 
-              .info-date-block {
-                @extend %t-title7;
-
-                color: $gray; // WCAG 2.0 AA compliant
+              .info-date-block-container {
                 display: block;
+
+                .info-date-block{
+                  @extend %t-title7;
+
+                  color: $gray; // WCAG 2.0 AA compliant
+
+                  .fa-close {
+                    color: theme-color("error");
+                  }
+
+                  .fa-check {
+                    color: theme-color("success");
+                  }
+                }
+
+                .change-session {
+                  @extend %t-title7;
+                  @include margin(0, 0, 0, $baseline/4);
+
+                  padding: 0;
+                  border: none;
+                  letter-spacing: normal;
+                }
               }
             }
 
@@ -633,18 +653,20 @@
     .message-copy .copy {
       @extend %t-copy-sub1;
 
-      margin: 2px 0 0 0;
+      margin: 2px 0 0;
     }
 
     // CASE: expandable
     &.is-expandable {
       .wrapper-tip {
-        .message-title, .message-copy {
+        .message-title,
+        .message-copy {
           margin-bottom: 0;
           display: inline-block;
         }
 
-        .message-title .value, .message-copy {
+        .message-title .value,
+        .message-copy {
           @include transition(color $tmg-f2 ease-in-out 0s);
         }
 
@@ -652,7 +674,9 @@
         &:hover {
           cursor: pointer;
 
-          .message-title .value, .message-copy, .ui-toggle-expansion {
+          .message-title .value,
+          .message-copy,
+          .ui-toggle-expansion {
             color: $link-color;
           }
         }
@@ -789,7 +813,7 @@
       .action-view-consent {
         @extend %btn-pl-white-base;
         @include float(right);
-              
+
         &.archived {
           @extend %btn-pl-default-base;
         }
@@ -1071,6 +1095,89 @@
       @include padding($baseline/2, $baseline, $baseline/2, $baseline/2);
     }
   }
+
+  // Course Entitlement Session Selection
+  .course-entitlement-selection-container {
+    background-color: theme-color("inverse");
+
+    .action-header {
+      padding-bottom: $baseline/4;
+      font-weight: $font-weight-bold;
+      color: theme-color("dark");
+    }
+
+    .action-controls {
+      display: flex;
+
+      .session-select {
+        background-color: theme-color("inverse");
+        height: $baseline*1.5;
+        flex-grow: 5;
+        margin-bottom: $baseline*0.4;
+      }
+
+      .enroll-btn-initial {
+        @include margin-left($baseline);
+
+        height: $baseline*1.5;
+        flex-grow: 1;
+        letter-spacing: 0;
+        background: theme-color("inverse");
+        border-color: theme-color("primary");
+        color: theme-color("primary");
+        text-shadow: none;
+        font-size: $font-size-base;
+        padding: 0;
+        box-shadow: none;
+        border-radius: $border-radius-sm;
+        transition: all 0.4s ease-out;
+
+        &:hover {
+          background: theme-color("primary");
+          border-color: theme-color("primary");
+          color: theme-color("inverse");
+        }
+      }
+
+      @include media-breakpoint-down(xs) {
+        flex-direction: column;
+
+        .enroll-btn-initial {
+          margin: $baseline/4 0 $baseline/4;
+        }
+      }
+    }
+
+    .popover {
+      .popover-title {
+        margin-bottom: $baseline/2;
+      }
+
+      .action-items {
+        display: flex;
+        justify-content: space-between;
+        margin-top: $baseline/2;
+
+        .final-confirmation-btn {
+          box-shadow: none;
+          border: 1px solid theme-color("dark");
+          background: none;
+          color: theme-color("dark");
+          text-shadow: none;
+          letter-spacing: 0;
+          flex-grow: 1;
+          margin: 0 $baseline/4;
+          padding: $baseline/10 $baseline;
+          font-size: $font-size-base;
+
+          &:hover {
+            background: theme-color("primary");
+            color: theme-color("inverse");
+          }
+        }
+      }
+    }
+  }
 }
 
 // CASE: empty dashboard
@@ -1323,11 +1430,11 @@ p.course-block {
   padding: 6px 32px 7px;
   text-align: center;
   margin-top: 16px;
-  opacity:0.5;
-  background:#808080;
-  border:0;
+  opacity: 0.5;
+  background: #808080;
+  border: 0;
   color: theme-color("inverse");
-  box-shadow:none;
+  box-shadow: none;
 
   &.archived {
     @include button(simple, $button-archive-color);
@@ -1557,5 +1664,4 @@ a.fade-cover {
     color: theme-color("inverse");
     text-decoration: none;
   }
-
 }
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index b70271f6bf8a15a17e437e68810a97f7a30144c6..c60337be24966dbdd1edf1eda928c03f95ca504a 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -6,11 +6,16 @@
 from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
 from django.template import RequestContext
+from entitlements.models import CourseEntitlement
 import third_party_auth
 from third_party_auth import pipeline
+from opaque_keys.edx.keys import CourseKey
+from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
 from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
 from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
 from openedx.core.djangolib.markup import HTML, Text
+
+from student.models import CourseEnrollment
 %>
 
 <%
@@ -108,7 +113,7 @@ from openedx.core.djangolib.markup import HTML, Text
         <div class="my-courses" id="my-courses">
           <%include file="learner_dashboard/_dashboard_navigation_courses.html"/>
 
-          % if len(course_enrollments) > 0:
+          % if len(course_entitlements + course_enrollments) > 0:
             <ul class="listing-courses">
             <%
                 share_settings = configuration_helpers.get_value(
@@ -116,20 +121,53 @@ from openedx.core.djangolib.markup import HTML, Text
                     getattr(settings, 'SOCIAL_SHARING_SETTINGS', {})
                 )
             %>
-            % for dashboard_index, enrollment in enumerate(course_enrollments):
-              <% show_courseware_link = (enrollment.course_id in show_courseware_links_for) %>
-              <% cert_status = cert_statuses.get(enrollment.course_id) %>
-              <% can_unenroll = (not cert_status) or cert_status.get('can_unenroll') %>
-              <% credit_status = credit_statuses.get(enrollment.course_id) %>
-              <% show_email_settings = (enrollment.course_id in show_email_settings_for) %>
-              <% course_mode_info = all_course_modes.get(enrollment.course_id) %>
-              <% is_paid_course = (enrollment.course_id in enrolled_courses_either_paid) %>
-              <% is_course_blocked = (enrollment.course_id in block_courses) %>
-              <% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %>
-              <% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %>
-              <% related_programs = inverted_programs.get(unicode(enrollment.course_id)) %>
-              <% show_consent_link = (enrollment.course_id in consent_required_courses) %>
-              <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
+            % for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments):
+              <%
+                # Check if the course run is an entitlement and if it has an associated session
+                entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None
+                entitlement_session = entitlement.enrollment_course_run if entitlement else None
+
+                is_fulfilled_entitlement = True if entitlement and entitlement_session else False
+                is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False
+
+                entitlement_available_sessions = []
+                if entitlement:
+                  # Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes
+                  entitlement_available_sessions = [{
+                    'session_id': course['key'],
+                    'enrollment_end': course['enrollment_end'],
+                    'pacing_type': course['pacing_type'],
+                    'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
+                    'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
+                    'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
+                    } for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
+                  if is_fulfilled_entitlement:
+                    # If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
+                    enrollment = entitlement_session
+                  else:
+                    # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
+                    upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
+                    next_session = upcoming_sessions[0] if upcoming_sessions else None
+                    if not next_session:
+                        continue
+                    enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
+
+                session_id = enrollment.course_id
+                show_courseware_link = (session_id in show_courseware_links_for)
+                cert_status = cert_statuses.get(session_id)
+                can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False
+                credit_status = credit_statuses.get(session_id)
+                show_email_settings = (session_id in show_email_settings_for)
+                course_mode_info = all_course_modes.get(session_id)
+                is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
+                is_course_blocked = (session_id in block_courses)
+                course_verification_status = verification_status_by_course.get(session_id, {})
+                course_requirements = courses_requirements_not_met.get(session_id)
+                related_programs = inverted_programs.get(unicode(session_id))
+                show_consent_link = (session_id in consent_required_courses)
+                course_overview = enrollment.course_overview
+              %>
+              <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
             % endfor
 
             </ul>
diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html
index 21a7c54139c94842b8efe1f5f8ee82a7513bb0b5..c09ad888944708fa188b12a76c88ac2af92d23c1 100644
--- a/lms/templates/dashboard/_dashboard_course_listing.html
+++ b/lms/templates/dashboard/_dashboard_course_listing.html
@@ -1,4 +1,4 @@
-<%page args="course_overview, enrollment, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard, show_consent_link, enterprise_customer_name" expression_filter="h"/>
+<%page args="course_overview, enrollment, entitlement, entitlement_session, course_card_index, is_unfulfilled_entitlement, is_fulfilled_entitlement, entitlement_available_sessions, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard, show_consent_link, enterprise_customer_name" expression_filter="h"/>
 
 <%!
 import urllib
@@ -59,11 +59,12 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
           lang="${course_overview.language}"
         % endif
       >
-<div class="course${mode_class}" aria-labelledby="course-title-${enrollment.course_id}">
+<div class="course${mode_class}" aria-labelledby="course-title-${enrollment.course_id}" id="course-card-${course_card_index}">
   <% course_target = reverse(course_home_url_name(course_overview.id), args=[unicode(course_overview.id)]) %>
-  <div class="details">
+  <section class="details" aria-labelledby="details-heading-${course_overview.number}">
+      <h2 class="hd hd-2 sr" id="details-heading-${course_overview.number}">${_('Course details')}</h2>
     <div class="wrapper-course-image" aria-hidden="true">
-      % if show_courseware_link:
+      % if show_courseware_link and not is_unfulfilled_entitlement:
         % if not is_course_blocked:
             <a href="${course_target}" data-course-key="${enrollment.course_id}" class="cover" tabindex="-1">
               <img src="${course_overview.image_urls['small']}" class="course-image" alt="${_('{course_number} {course_name} Home Page').format(course_number=course_overview.number, course_name=course_overview.display_name_with_default)}" />
@@ -90,7 +91,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
     </div>
       <div class="wrapper-course-details">
         <h3 class="course-title" id="course-title-${enrollment.course_id}">
-          % if show_courseware_link:
+          % if show_courseware_link and not is_unfulfilled_entitlement:
             % if not is_course_blocked:
               <a data-course-key="${enrollment.course_id}" href="${course_target}">${course_overview.display_name_with_default}</a>
             % else:
@@ -126,18 +127,27 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
             endif
           %>
 
-                % if isinstance(course_date, basestring):
-                    <span class="info-date-block" data-tooltip="Hi">${_(container_string).format(date=course_date)}</span>
-                % elif course_date is not None:
-                    <%
-                        course_date_string = course_date.strftime('%Y-%m-%dT%H:%M:%S%z')
-                    %>
-                    <span class="info-date-block localized-datetime" data-language="${user_language}" data-tooltip="Hi" data-timezone="${user_timezone}" data-datetime="${course_date_string}" data-format=${format} data-string="${container_string}"></span>
+            <span class="info-date-block-container">
+                % if is_unfulfilled_entitlement:
+                    <span class="info-date-block" aria-live="polite">
+                        <span class="icon fa fa-warning" aria-hidden="true"></span>
+                        ${_('You must select a session to access the course.')}
+                    </span>
+                % else:
+                    % if isinstance(course_date, basestring):
+                        <span class="info-date-block">${container_string.format(date=course_date)}</span>
+                    % elif course_date is not None:
+                        <span class="info-date-block localized-datetime" data-language="${user_language}" data-timezone="${user_timezone}" data-datetime="${course_date.strftime('%Y-%m-%dT%H:%M:%S%z')}" data-format=${format} data-string="${container_string}"></span>
+                    % endif
                 % endif
+                % if entitlement:
+                    <button class="change-session btn-link ${'hidden' if is_unfulfilled_entitlement else ''}" aria-controls="change-session-${str(entitlement.uuid)}">${_('Change Session')}</button>
+                % endif
+            </span>
         </div>
         <div class="wrapper-course-actions">
           <div class="course-actions">
-            % if show_courseware_link:
+            % if show_courseware_link or is_unfulfilled_entitlement:
               % if course_overview.has_ended():
                 % if not is_course_blocked:
                   <a href="${course_target}" class="enter-course archived" data-course-key="${enrollment.course_id}">${_('View Archived Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
@@ -146,7 +156,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
                 % endif
               % else:
                 % if not is_course_blocked:
-                  <a href="${course_target}" class="enter-course" data-course-key="${enrollment.course_id}">${_('View Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
+                  <a href="${course_target}" class="enter-course ${'hidden' if is_unfulfilled_entitlement else ''}" data-course-key="${enrollment.course_id}">${_('View Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
                 % else:
                   <a class="enter-course-blocked" data-course-key="${enrollment.course_id}">${_('View Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
                 % endif
@@ -205,68 +215,91 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
                         </a>
                     % endif
                 % endif
-
             % endif
 
-            <div class="wrapper-action-more" data-course-key="${enrollment.course_id}">
-              <button type="button" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-${dashboard_index}" data-course-number="${course_overview.number}" data-course-name="${course_overview.display_name_with_default}" data-dashboard-index="${dashboard_index}">
-                <span class="sr">${_('Course options for')}</span>
-                <span class="sr">&nbsp;
-                  ${course_overview.display_name_with_default}
-                </span>
-                <span class="fa fa-cog" aria-hidden="true"></span>
-              </button>
-              <div class="actions-dropdown" id="actions-dropdown-${dashboard_index}" tabindex="-1">
-                <ul class="actions-dropdown-list" id="actions-dropdown-list-${dashboard_index}" aria-label="${_('Available Actions')}" role="menu">
-                  % if can_unenroll:
-                    <li class="actions-item" id="actions-item-unenroll-${dashboard_index}" role="menuitem">
-                      <% course_refund_url = reverse('course_run_refund_status', args=[unicode(course_overview.id)]) %>
+            % if not entitlement:
+                <div class="wrapper-action-more" data-course-key="${enrollment.course_id}">
+                  <button type="button" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-${dashboard_index}" data-course-number="${course_overview.number}" data-course-name="${course_overview.display_name_with_default}" data-dashboard-index="${dashboard_index}">
+                    <span class="sr">${_('Course options for')}</span>
+                    <span class="sr">&nbsp;
+                      ${course_overview.display_name_with_default}
+                    </span>
+                    <span class="fa fa-cog" aria-hidden="true"></span>
+                  </button>
+                  <div class="actions-dropdown" id="actions-dropdown-${dashboard_index}" tabindex="-1">
+                    <ul class="actions-dropdown-list" id="actions-dropdown-list-${dashboard_index}" aria-label="${_('Available Actions')}" role="menu">
+                      % if can_unenroll:
+                        <li class="actions-item" id="actions-item-unenroll-${dashboard_index}" role="menuitem">
+                          <% course_refund_url = reverse('course_run_refund_status', args=[unicode(course_overview.id)]) %>
+                              % if not is_course_blocked:
+                              <a href="#unenroll-modal" class="action action-unenroll" rel="leanModal"
+                                 data-course-id="${course_overview.id}"
+                                 data-course-number="${course_overview.number}"
+                                 data-course-name="${course_overview.display_name_with_default}"
+                                 data-dashboard-index="${dashboard_index}"
+                                 data-course-refund-url="${course_refund_url}"
+                                 data-course-is-paid-course="${is_paid_course}"
+                                 data-course-cert-name-long="${cert_name_long}"
+                                 data-course-enrollment-mode="${enrollment.mode}">
+                                ${_('Unenroll')}
+                              </a>
+                              % else:
+                              <a class="action action-unenroll is-disabled"
+                                 data-course-id="${course_overview.id}"
+                                 data-course-number="${course_overview.number}"
+                                 data-course-name="${course_overview.display_name_with_default}"
+                                 data-dashboard-index="${dashboard_index}"
+                                 data-course-refund-url="${course_refund_url}"
+                                 data-course-is-paid-course="${is_paid_course}"
+                                 data-course-cert-name-long="${cert_name_long}"
+                                 data-course-enrollment-mode="${enrollment.mode}">
+                                ${_('Unenroll')}
+                              </a>
+                            % endif
+                        </li>
+                    % endif
+                      <li class="actions-item" id="actions-item-email-settings-${dashboard_index}" role="menuitem">
+                        % if show_email_settings:
                           % if not is_course_blocked:
-                          <a href="#unenroll-modal" class="action action-unenroll" rel="leanModal"
-                             data-course-id="${course_overview.id}"
-                             data-course-number="${course_overview.number}"
-                             data-course-name="${course_overview.display_name_with_default}"
-                             data-dashboard-index="${dashboard_index}"
-                             data-course-refund-url="${course_refund_url}"
-                             data-course-is-paid-course="${is_paid_course}"
-                             data-course-cert-name-long="${cert_name_long}"
-                             data-course-enrollment-mode="${enrollment.mode}">
-                            ${_('Unenroll')}
-                          </a>
+                            <a href="#email-settings-modal" class="action action-email-settings" rel="leanModal" data-course-id="${course_overview.id}" data-course-number="${course_overview.number}" data-dashboard-index="${dashboard_index}" data-optout="${unicode(course_overview.id) in course_optouts}">${_('Email Settings')}</a>
                           % else:
-                          <a class="action action-unenroll is-disabled"
-                             data-course-id="${course_overview.id}"
-                             data-course-number="${course_overview.number}"
-                             data-course-name="${course_overview.display_name_with_default}"
-                             data-dashboard-index="${dashboard_index}"
-                             data-course-refund-url="${course_refund_url}"
-                             data-course-is-paid-course="${is_paid_course}"
-                             data-course-cert-name-long="${cert_name_long}"
-                             data-course-enrollment-mode="${enrollment.mode}">
-                            ${_('Unenroll')}
-                          </a>
+                            <a class="action action-email-settings is-disabled" data-course-id="${course_overview.id}" data-course-number="${course_overview.number}" data-dashboard-index="${dashboard_index}" data-optout="${unicode(course_overview.id) in course_optouts}">${_('Email Settings')}</a>
+                          % endif
                         % endif
-                    </li>
-                % endif
-                  <li class="actions-item" id="actions-item-email-settings-${dashboard_index}" role="menuitem">
-                    % if show_email_settings:
-                      % if not is_course_blocked:
-                        <a href="#email-settings-modal" class="action action-email-settings" rel="leanModal" data-course-id="${course_overview.id}" data-course-number="${course_overview.number}" data-dashboard-index="${dashboard_index}" data-optout="${unicode(course_overview.id) in course_optouts}">${_('Email Settings')}</a>
-                      % else:
-                        <a class="action action-email-settings is-disabled" data-course-id="${course_overview.id}" data-course-number="${course_overview.number}" data-dashboard-index="${dashboard_index}" data-optout="${unicode(course_overview.id) in course_optouts}">${_('Email Settings')}</a>
-                      % endif
-                    % endif
-                  </li>
-                </ul>
-              </div>
-            </div>
+                      </li>
+                    </ul>
+                  </div>
+                </div>
+            % endif
           </div>
         </div>
       </div>
-  </div>
+  </section>
   <footer class="wrapper-messages-primary">
     <div class="messages-list">
-      % if related_programs:
+
+      % if entitlement:
+        <div class="course-entitlement-selection-container ${'' if is_unfulfilled_entitlement else 'hidden'}"></div>
+        <%static:require_module module_name="js/learner_dashboard/course_entitlement_factory" class_name="EntitlementFactory">
+          EntitlementFactory({
+              el: '${ '#course-card-' + str(course_card_index) + ' .course-entitlement-selection-container' | n, js_escaped_string }',
+              triggerOpenBtn: '${ '#course-card-' + str(course_card_index) + ' .change-session' | n, js_escaped_string }',
+              courseCardMessages: '${ '#course-card-' + str(course_card_index) + ' .messages-list > .message' | n, js_escaped_string }',
+              courseTitleLink: '${ '#course-card-' + str(course_card_index) + ' .course-title a' | n, js_escaped_string }',
+              courseImageLink: '${ '#course-card-' + str(course_card_index) + ' .wrapper-course-image > a' | n, js_escaped_string }',
+              dateDisplayField: '${ '#course-card-' + str(course_card_index) + ' .info-date-block' | n, js_escaped_string }',
+              enterCourseBtn: '${ '#course-card-' + str(course_card_index) + ' .enter-course' | n, js_escaped_string }',
+              availableSessions: '${ entitlement_available_sessions | n, dump_js_escaped_json }',
+              entitlementUUID: '${ entitlement.course_uuid | n, js_escaped_string }',
+              currentSessionId: '${ entitlement_session.course_id if entitlement_session else '' | n, js_escaped_string }',
+              userId: '${ user.id | n, js_escaped_string }',
+              enrollUrl: '${ reverse('entitlements_api:v1:enrollments', args=[str(entitlement.uuid)]) | n, js_escaped_string }',
+              courseHomeUrl: '${ course_target | n, js_escaped_string }'
+          });
+        </%static:require_module>
+      %endif
+
+      % if related_programs and not entitlement:
       <div class="message message-related-programs is-shown">
         <span class="related-programs-preface" tabindex="0">${_('Related Programs')}:</span>
         <ul>
@@ -358,7 +391,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
           </div>
         % endif
 
-        % if course_mode_info['show_upsell']:
+        % if course_mode_info and course_mode_info['show_upsell'] and not entitlement:
           <div class="message message-upsell has-actions is-shown">
             <div class="wrapper-extended">
               <p class="message-copy" align="justify">
@@ -410,7 +443,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
         % endif
     </div>
   </footer>
-</div>
+</article>
 </div>
 </li>
 <script>
diff --git a/lms/templates/learner_dashboard/course_entitlement.underscore b/lms/templates/learner_dashboard/course_entitlement.underscore
new file mode 100644
index 0000000000000000000000000000000000000000..48a2730d409ec862095aff2faa70926cdba707a3
--- /dev/null
+++ b/lms/templates/learner_dashboard/course_entitlement.underscore
@@ -0,0 +1,33 @@
+<div id="change-session-<%- entitlementUUID %>" class="message is-shown">
+    <div class="action-header">
+        <% if (currentSessionId) { %>
+            <%- gettext('Change to a different session or leave the current session.')%>
+        <% } else { %>
+            <%- gettext('To access the course, select a session.')%>
+        <% } %>
+    </div>
+    <div class="action-controls">
+        <select class="session-select" aria-label="<%- StringUtils.interpolate( gettext('Session Selection Dropdown for {courseName}'), { courseName: courseName }) %>">
+            <% _.each(availableSessions, function(session) { %>
+                <option data-session_id="<%- session.session_id %>">
+                    <% if (session.session_id === currentSessionId) { %>
+                        <%- StringUtils.interpolate( gettext('{sessionDates} - Currently Selected'), {sessionDates: session.session_dates}) %>
+                    <% } else if (session.enrollment_end){ %>
+                        <%- StringUtils.interpolate( gettext('{sessionDates} (Open until {enrollmentEnd})'), {sessionDates: session.session_dates, enrollmentEnd: session.enrollment_end}) %>
+                    <% } else { %>
+                        <%- session.session_dates %>
+                    <% } %>
+                </option>
+            <% }) %>
+            <option disabled><%- gettext('More sessions coming soon') %></option>
+            <% if (currentSessionId){%> <option><%- gettext('Leave the current session and decide later')%></option><% } %>
+        </select>
+        <button class="enroll-btn-initial">
+            <% if (currentSessionId) { %>
+                <%- gettext('Change Session') %>
+            <% } else { %>
+                <%- gettext('Select Session') %>
+            <% } %>
+        </button>
+    </div>
+</div>
diff --git a/lms/templates/learner_dashboard/verification_popover.underscore b/lms/templates/learner_dashboard/verification_popover.underscore
new file mode 100644
index 0000000000000000000000000000000000000000..7191fe8e61887c933d17d2c6500b4a372314f1e3
--- /dev/null
+++ b/lms/templates/learner_dashboard/verification_popover.underscore
@@ -0,0 +1,16 @@
+<div class="verification-modal" role="dialog" aria-labelledby="enrollment-verification-title">
+    <p id="enrollment-verification-title">
+        <div class="popover-title">
+            <%- confirmationMsgTitle %>
+        </div>
+        <%- confirmationMsgBody %>
+    </p>
+    <div class="action-items">
+        <button type="button" class="popover-dismiss final-confirmation-btn">
+             <%- gettext('Cancel') %>
+        </button>
+        <button type="button" class="enroll-btn final-confirmation-btn">
+             <%- gettext('OK') %>
+        </button>
+     </div>
+ </div>
\ No newline at end of file
diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py
index 4f06845900ef7bf4ee53064a5764ec9d81986a1d..6ba2aa638105f73e9a5e920d2a8639b12af51f8c 100644
--- a/openedx/core/djangoapps/catalog/utils.py
+++ b/openedx/core/djangoapps/catalog/utils.py
@@ -233,7 +233,7 @@ def get_course_runs_for_course(course_uuid):
             resource_id=course_uuid,
             api=api,
             cache_key=cache_key if catalog_integration.is_cache_enabled else None,
-            long_term_cache=True
+            long_term_cache=True,
         )
         return data.get('course_runs', [])
     else:
diff --git a/themes/edx.org/lms/templates/dashboard.html b/themes/edx.org/lms/templates/dashboard.html
index b9af125de1317605d243809a5db70cc728f14437..32eba8b28546950fb6da019c1f2ee0837a090bb4 100644
--- a/themes/edx.org/lms/templates/dashboard.html
+++ b/themes/edx.org/lms/templates/dashboard.html
@@ -9,9 +9,14 @@ import third_party_auth
 from third_party_auth import pipeline
 from django.core.urlresolvers import reverse
 import json
+from opaque_keys.edx.keys import CourseKey
+from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
+from openedx.core.djangoapps.theming import helpers as theming_helpers
 from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
 from openedx.core.djangolib.markup import HTML, Text
-from openedx.core.djangoapps.theming import helpers as theming_helpers
+
+from entitlements.models import CourseEntitlement
+from student.models import CourseEnrollment
 %>
 
 <%
@@ -108,28 +113,58 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
     <header class="wrapper-header-courses">
       <h2 class="header-courses">${_("My Courses")}</h2>
     </header>
+    % if len(course_entitlements + course_enrollments) > 0:
+        <ul class="listing-courses">
+        <% share_settings = getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}) %>
+        % for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments):
+          <%
+            # Check if the course run is an entitlement and if it has an associated session
+            entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None
+            entitlement_session = entitlement.enrollment_course_run if entitlement else None
 
+            is_fulfilled_entitlement = True if entitlement and entitlement_session else False
+            is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False
 
-    % if len(course_enrollments) > 0:
-      <ul class="listing-courses">
-      <% share_settings = getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}) %>
-      % for dashboard_index, enrollment in enumerate(course_enrollments):
-        <% show_courseware_link = (enrollment.course_id in show_courseware_links_for) %>
-        <% cert_status = cert_statuses.get(enrollment.course_id) %>
-        <% can_unenroll = (not cert_status) or cert_status.get('can_unenroll') %>
-        <% credit_status = credit_statuses.get(enrollment.course_id) %>
-        <% show_email_settings = (enrollment.course_id in show_email_settings_for) %>
-        <% course_mode_info = all_course_modes.get(enrollment.course_id) %>
-        <% is_paid_course = (enrollment.course_id in enrolled_courses_either_paid) %>
-        <% is_course_blocked = (enrollment.course_id in block_courses) %>
-        <% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %>
-        <% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %>
-        <% related_programs = inverted_programs.get(unicode(enrollment.course_id)) %>
-        <% show_consent_link = (enrollment.course_id in consent_required_courses) %>
-        <%include file = 'dashboard/_dashboard_course_listing.html' args='course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
-      % endfor
+            entitlement_available_sessions = []
+            if entitlement:
+              # Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes
+              entitlement_available_sessions = [{
+                'session_id': course['key'],
+                'enrollment_end': course['enrollment_end'],
+                'pacing_type': course['pacing_type'],
+                'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
+                'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
+                'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
+                } for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
+              if is_fulfilled_entitlement:
+                # If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
+                enrollment = entitlement_session
+              else:
+                # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
+                upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
+                next_session = upcoming_sessions[0] if upcoming_sessions else None
+                if not next_session:
+                    continue
+                enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
 
-      </ul>
+            session_id = enrollment.course_id
+            show_courseware_link = (session_id in show_courseware_links_for)
+            cert_status = cert_statuses.get(session_id)
+            can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False
+            credit_status = credit_statuses.get(session_id)
+            show_email_settings = (session_id in show_email_settings_for)
+            course_mode_info = all_course_modes.get(session_id)
+            is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
+            is_course_blocked = (session_id in block_courses)
+            course_verification_status = verification_status_by_course.get(session_id, {})
+            course_requirements = courses_requirements_not_met.get(session_id)
+            related_programs = inverted_programs.get(unicode(session_id))
+            show_consent_link = (session_id in consent_required_courses)
+            course_overview = enrollment.course_overview
+          %>
+          <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
+        % endfor
+        </ul>
     % else:
       <section class="empty-dashboard-message">
         <p>${_("You are not enrolled in any courses yet.")}</p>