diff --git a/packages/component-fixture-manager/src/fixtures/users.js b/packages/component-fixture-manager/src/fixtures/users.js
index 67ecfa32f8fdea77d8bed466eb85dfd563daf1e4..5d8872ba8cd34c9c9b63aec724df9b38a5dfec29 100644
--- a/packages/component-fixture-manager/src/fixtures/users.js
+++ b/packages/component-fixture-manager/src/fixtures/users.js
@@ -85,6 +85,7 @@ const users = {
     isConfirmed: false,
     updateProperties: jest.fn(() => users.user),
     teams: [],
+    confirmationToken: chance.hash(),
   },
   author: {
     type: 'user',
@@ -102,6 +103,7 @@ const users = {
     passwordResetToken: chance.hash(),
     passwordResetTimestamp: Date.now(),
     teams: [authorTeamID],
+    confirmationToken: chance.hash(),
   },
   reviewer: {
     type: 'user',
diff --git a/packages/component-user-manager/src/Users.js b/packages/component-user-manager/src/Users.js
index c86df66c9d97bc496731d743faf7831c6f94b832..1c5c159ddfd35f3d654dc3e593c1c74acaed283e 100644
--- a/packages/component-user-manager/src/Users.js
+++ b/packages/component-user-manager/src/Users.js
@@ -38,7 +38,33 @@ const Invite = app => {
     '/api/users/reset-password',
     require('./routes/users/resetPassword')(app.locals.models),
   )
-
+  /**
+   * @api {post} /api/users/confirm Confirm user
+   * @apiGroup Users
+   * @apiParamExample {json} Body
+   *    {
+   *      "userId": "valid-user-id",
+   *      "confirmationToken": "12312321"
+   *    }
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *    {
+   *      "id": "a6184463-b17a-42f8-b02b-ae1d755cdc6b",
+   *      "type": "user",
+   *      "admin": false,
+   *      "email": "email@example.com",
+   *      "teams": [],
+   *      "username": "email@example.com",
+   *      "fragments": [],
+   *      "collections": [],
+   *      "isConfirmed": true,
+   *      "editorInChief": false,
+   *      "handlingEditor": false
+   *    }
+   * @apiErrorExample {json} Forgot Password errors
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   */
   app.post(
     '/api/users/confirm',
     require('./routes/users/confirm')(app.locals.models),
diff --git a/packages/component-user-manager/src/routes/users/confirm.js b/packages/component-user-manager/src/routes/users/confirm.js
index 2675e160efbd20e970c6488240b704a90464a80e..ace2592bff912eaa12bb8b6ac0a7c26fee52c61e 100644
--- a/packages/component-user-manager/src/routes/users/confirm.js
+++ b/packages/component-user-manager/src/routes/users/confirm.js
@@ -16,7 +16,7 @@ module.exports = ({ User }) => async (req, res) => {
     }
 
     if (user.isConfirmed)
-      return res.status(400).json({ error: 'User is already confirmed' })
+      return res.status(400).json({ error: 'User is already confirmed.' })
 
     user.isConfirmed = true
     delete user.confirmationToken
@@ -27,6 +27,9 @@ module.exports = ({ User }) => async (req, res) => {
       token: token.create(user),
     })
   } catch (e) {
-    throw e
+    const notFoundError = await services.handleNotFoundError(e, 'User')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
   }
 }
diff --git a/packages/component-user-manager/src/tests/users/confirm.test.js b/packages/component-user-manager/src/tests/users/confirm.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f139d1f227c51ea1f8e990464921a7a1da01068
--- /dev/null
+++ b/packages/component-user-manager/src/tests/users/confirm.test.js
@@ -0,0 +1,83 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const httpMocks = require('node-mocks-http')
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+
+const { Model, fixtures } = fixturesService
+
+const { user, author } = fixtures.users
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+}))
+
+const reqBody = {
+  userId: user.id,
+  confirmationToken: user.confirmationToken,
+}
+
+const notFoundError = new Error()
+notFoundError.name = 'NotFoundError'
+notFoundError.status = 404
+const forgotPasswordPath = '../../routes/users/confirm'
+
+describe('Users confirm route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+
+  it('should return an error when some parameters are missing', async () => {
+    delete body.userId
+    const req = httpMocks.createRequest({ body })
+
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Missing required params')
+  })
+  it('should return an error when the confirmation token does not match', async () => {
+    body.confirmationToken = 'invalid-token'
+
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Wrong confirmation token.')
+  })
+  it('should return an error when the user does not exist', async () => {
+    body.userId = 'invalid-user-id'
+
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('User not found')
+  })
+  it('should return an error when the user is already confirmed', async () => {
+    body.userId = author.id
+    body.confirmationToken = author.confirmationToken
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('User is already confirmed.')
+  })
+  it('should return success when the body is correct', async () => {
+    const req = httpMocks.createRequest({ body })
+    const res = httpMocks.createResponse()
+    await require(forgotPasswordPath)(models)(req, res)
+    const data = JSON.parse(res._getData())
+    expect(data.token).toBeDefined()
+  })
+})
diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js
index c282bbfb1667d42dadfa2b1e00a47c0ede992835..19ac346db1ac276c83197297b728bbcf89cb83cc 100644
--- a/packages/xpub-faraday/config/default.js
+++ b/packages/xpub-faraday/config/default.js
@@ -40,6 +40,7 @@ module.exports = {
     port: 3000,
     logger,
     uploads: 'uploads',
+    secret: 'SECRET',
   },
   'pubsweet-client': {
     API_ENDPOINT: '/api',