diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index b0f71f1..404b176 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -239,12 +239,8 @@ function comment_menu() {
   );
   $items['comment/%comment/approve'] = array(
     'title' => 'Approve',
-    'page callback' => 'comment_approve',
-    'page arguments' => array(1),
-    'access callback' => 'entity_page_access',
-    'access arguments' => array(1, 'approve'),
-    'file' => 'comment.pages.inc',
     'weight' => 10,
+    'route_name' => 'comment_approve',
   );
   $items['comment/%comment/delete'] = array(
     'title' => 'Delete',
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index 9d7414b..4998f83 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -99,27 +99,3 @@ function comment_reply(EntityInterface $node, $pid = NULL) {
 
   return $build;
 }
-
-/**
- * Page callback: Publishes the specified comment.
- *
- * @param \Drupal\comment\Plugin\Core\Entity\Comment $comment
- *   A comment entity.
- *
- * @see comment_menu()
- */
-function comment_approve(Comment $comment) {
-  // @todo CSRF tokens are validated in page callbacks rather than access
-  //   callbacks, because access callbacks are also invoked during menu link
-  //   generation. Add token support to routing: http://drupal.org/node/755584.
-  $token = drupal_container()->get('request')->query->get('token');
-  if (!isset($token) || !drupal_valid_token($token, 'comment/' . $comment->id() . '/approve')) {
-    throw new AccessDeniedHttpException();
-  }
-
-  $comment->status->value = COMMENT_PUBLISHED;
-  $comment->save();
-
-  drupal_set_message(t('Comment approved.'));
-  return new RedirectResponse('node/' . $comment->nid->target_id, array('absolute' => TRUE));
-}
diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml
index 6f786dd..eaaeba4 100644
--- a/core/modules/comment/comment.routing.yml
+++ b/core/modules/comment/comment.routing.yml
@@ -1,7 +1,14 @@
 comment_edit_page:
-    pattern: 'comment/{comment}/edit'
-    defaults:
-        _entity_form: comment.default
-    requirements:
-        _entity_access: comment.update
+  pattern: '/comment/{comment}/edit'
+  defaults:
+    _entity_form: 'comment.default'
+  requirements:
+    _entity_access: 'comment.update'
 
+comment_approve:
+  pattern: '/comment/{comment}/approve'
+  defaults:
+    _content: '\Drupal\comment\Controller\CommentController::commentApprove'
+    entity_type: 'comment'
+  requirements:
+    _entity_access: 'comment.approve'
diff --git a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
new file mode 100644
index 0000000..7189584
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment\Controller\CommentController.
+ */
+
+namespace Drupal\comment\Controller;
+
+use Drupal\comment\CommentInterface;
+use Drupal\comment\Plugin\Core\Entity\Comment;
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Routing\PathBasedGeneratorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Controller for the comment entity.
+ *
+ * @see \Drupal\comment\Plugin\Core\Entity\Comment.
+ */
+class CommentController implements ControllerInterface {
+
+  /**
+   * The url generator service.
+   *
+   * @var \Drupal\Core\Routing\PathBasedGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * Constructs a CommentController object.
+   *
+   * @param \Drupal\Core\Routing\PathBasedGeneratorInterface $url_generator
+   *   The url generator service.
+   */
+  public function __construct(PathBasedGeneratorInterface $url_generator) {
+    $this->urlGenerator = $url_generator;
+  }
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(\Drupal::urlGenerator());
+  }
+
+  /**
+   * Publishes the specified comment.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param \Drupal\comment\CommentInterface $comment
+   *   A comment entity.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse.
+   */
+  public function commentApprove(Request $request, CommentInterface $comment) {
+    // @todo CRSF tokens are validated in the content controller until it gets
+    //   moved to the access layer:
+    //   Integrate CSRF link token directly into routing system:
+    //   https://drupal.org/node/1798296.
+    $token = $request->query->get('token');
+    if (!isset($token) || !drupal_valid_token($token, 'comment/' . $comment->id() . '/approve')) {
+      throw new AccessDeniedHttpException();
+    }
+
+    $comment->status->value = COMMENT_PUBLISHED;
+    $comment->save();
+
+    drupal_set_message(t('Comment approved.'));
+    $permalink_uri = $comment->permalink();
+    $permalink_uri['options']['absolute'] = TRUE;
+    $url = $this->urlGenerator->generateFromPath($permalink_uri['path'], $permalink_uri['options']);
+    return new RedirectResponse($url);
+  }
+
+}
