diff --git a/packages/component-app/src/components/App.js b/packages/component-app/src/components/App.js
index 3e86309b0e7b3c6daac3b67a13a3e0c676d81328..6a2a93b13962bd12d28418f330fb9b8247b33931 100644
--- a/packages/component-app/src/components/App.js
+++ b/packages/component-app/src/components/App.js
@@ -31,7 +31,7 @@ class App extends React.Component  {
           brandLink="/"
           userName={currentUser ? currentUser.username : null}
           loginLink="/login"
-          logoutLink="/signout"/>
+          logoutLink="/logout"/>
 
         <div className={classes.main}>
           {children}
@@ -41,21 +41,6 @@ class App extends React.Component  {
   }
 }
 
-/*const App = ({ children, currentUser }) => (
-  <div className={classes.root}>
-    <AppBar
-      appName={journal.metadata.name}
-      appLink="/projects" // TODO: make configurable
-      userName={currentUser ? currentUser.username : null}
-      loginLink="/signin"
-      logoutLink="/signout"/>
-
-    <div className={classes.main}>
-      {children}
-    </div>
-  </div>
-)*/
-
 App.propTypes = {
   children: PropTypes.node,
   currentUser: PropTypes.object
diff --git a/packages/component-authentication/src/components/AuthenticatedPage.js b/packages/component-authentication/src/components/AuthenticatedPage.js
index 3279c74e11e2c06e8c667b5942c3b466de0b7da6..f2a62da9d85013e274b441e6b4e5fb7e4aa67471 100644
--- a/packages/component-authentication/src/components/AuthenticatedPage.js
+++ b/packages/component-authentication/src/components/AuthenticatedPage.js
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types'
 import { compose } from 'recompose'
 import { connect } from 'react-redux'
 import { push } from 'react-router-redux'
-import actions from 'pubsweet-client/src/actions'
 import { withRouter } from 'react-router'
+import { getCurrentUser } from '../redux/currentUser'
 
 class AuthenticatedPage extends React.Component {
   componentDidMount () {
@@ -52,7 +52,7 @@ export default compose(
       isFetching: state.currentUser.isFetching
     }),
     {
-      getCurrentUser: actions.getCurrentUser,
+      getCurrentUser,
       push
     }
   ),
diff --git a/packages/component-authentication/src/components/Login.js b/packages/component-authentication/src/components/Login.js
index 23ad332d1cbf2c1f150522b610464705161772bd..6747e67bb8bc672086f7951289687b70dcd97909 100644
--- a/packages/component-authentication/src/components/Login.js
+++ b/packages/component-authentication/src/components/Login.js
@@ -1,19 +1,17 @@
 import React from 'react'
-import classnames from 'classnames'
 import { Field } from 'redux-form'
+import { Link } from 'react-router'
 import classes from './Login.local.css'
 
-const Login = ({ loginError, handleSubmit }) => (
+const Login = ({ error, handleSubmit }) => (
   <div className={classes.root}>
     <div className={classes.title}>
       Login
     </div>
 
-    {loginError && <div className={classes.error}>{loginError}</div>}
+    {error && <div className={classes.error}>{error.message}</div>}
 
-    <form
-      onSubmit={handleSubmit}
-      className={classnames({ error: !!loginError, success: !loginError })}>
+    <form onSubmit={handleSubmit}>
       <div>
         <label>
           Username
@@ -32,6 +30,8 @@ const Login = ({ loginError, handleSubmit }) => (
         <button type="submit" className={classes.button}>Login</button>
       </div>
     </form>
+
+    <div>or <Link to="/signup">sign up</Link></div>
   </div>
 )
 
diff --git a/packages/component-authentication/src/components/LoginPage.js b/packages/component-authentication/src/components/LoginPage.js
index 53944b07e31dac1b5f714818eb88cfeeb19d2d84..f12210a15d6af7f2e9a0d962effdaf0f684fa2e7 100644
--- a/packages/component-authentication/src/components/LoginPage.js
+++ b/packages/component-authentication/src/components/LoginPage.js
@@ -1,21 +1,29 @@
-import { connect } from 'react-redux'
-import { loginUser } from 'pubsweet-component-login/actions'
-import { reduxForm } from 'redux-form'
+import { reduxForm, SubmissionError } from 'redux-form'
 import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { login } from '../redux/login'
 import Login from './Login'
 
 // TODO: const redirect = this.props.location.query.next | CONFIG['pubsweet-client']['login-redirect']
 
+const onSubmit = (values, dispatch) => {
+  dispatch(login(values)).catch(error => {
+    if (error.validationErrors) {
+      throw new SubmissionError(error.validationErrors)
+    } else {
+      //
+    }
+  })
+}
+
 export default compose(
   reduxForm({
-    form: 'login'
+    form: 'login',
+    onSubmit
   }),
   connect(
     state => ({
-      loginError: state.error
-    }),
-    {
-      onSubmit: loginUser
-    }
+      error: state.login.error
+    })
   )
 )(Login)
diff --git a/packages/component-authentication/src/components/LogoutPage.js b/packages/component-authentication/src/components/LogoutPage.js
index ec226a3abf4205416362fc45dd559339d35a0c5a..266f83d766355f91a2cda332ce5d2014b31bedf9 100644
--- a/packages/component-authentication/src/components/LogoutPage.js
+++ b/packages/component-authentication/src/components/LogoutPage.js
@@ -1,21 +1,21 @@
 import React from 'react'
 import { compose } from 'recompose'
 import { connect } from 'react-redux'
-import { logoutUser } from 'pubsweet-component-login/actions'
+import { logout } from '../redux/logout'
 
 class Logout extends React.Component {
   componentDidMount () {
-    const { isAuthenticated, logoutUser } = this.props
+    const { isAuthenticated, logout } = this.props
 
     if (isAuthenticated)  {
-      logoutUser()
+      logout()
     }
   }
 
   render () {
     const { isAuthenticated } = this.props
 
-    return isAuthenticated ? 'Signed out' : 'Signing out…'
+    return isAuthenticated ? <div>Signing out…</div> : <div>Signed out</div>
   }
 }
 
@@ -25,7 +25,7 @@ export default compose(
       isAuthenticated: state.currentUser.isAuthenticated,
     }),
     {
-      logoutUser
+      logout
     }
   )
 )(Logout)
diff --git a/packages/component-authentication/src/components/Signup.js b/packages/component-authentication/src/components/Signup.js
index 0528213bd1727e6587dd48f851deb7c03ce25a2e..d85789102d2414fdeb7983236ddaff8e6d7eddba 100644
--- a/packages/component-authentication/src/components/Signup.js
+++ b/packages/component-authentication/src/components/Signup.js
@@ -1,6 +1,6 @@
 import React from 'react'
-import classnames from 'classnames'
 import { Field } from 'redux-form'
+import { Link } from 'react-router'
 import classes from './Signup.local.css'
 
 const Signup = ({ error, handleSubmit }) => (
@@ -9,11 +9,9 @@ const Signup = ({ error, handleSubmit }) => (
       Sign up
     </div>
 
-    {error && <div className={classes.error}>{error}</div>}
+    {error && <div className={classes.error}>{error.message}</div>}
 
-    <form
-      onSubmit={handleSubmit}
-      className={classnames({ error: !!error, success: !error })}>
+    <form onSubmit={handleSubmit}>
       <div>
         <label>
           Username
@@ -39,6 +37,8 @@ const Signup = ({ error, handleSubmit }) => (
         <button type="submit" className={classes.button}>Sign up</button>
       </div>
     </form>
+
+    <div>or <Link to="/login">login</Link></div>
   </div>
 )
 
diff --git a/packages/component-authentication/src/components/Signup.local.css b/packages/component-authentication/src/components/Signup.local.css
index ee7dba2315cbe848026d243951aa45c13ddf5c12..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/packages/component-authentication/src/components/Signup.local.css
+++ b/packages/component-authentication/src/components/Signup.local.css
@@ -1,3 +0,0 @@
-.error {
-  color: red;
-}
diff --git a/packages/component-authentication/src/components/SignupPage.js b/packages/component-authentication/src/components/SignupPage.js
index c6313ef4c6820977b9951af7eb184a47c822b94e..b6f85aeecb911148003d1c4ecd5fff1ba4f305f2 100644
--- a/packages/component-authentication/src/components/SignupPage.js
+++ b/packages/component-authentication/src/components/SignupPage.js
@@ -1,19 +1,27 @@
-import { compose } from 'recompose'
 import { connect } from 'react-redux'
-import { reduxForm } from 'redux-form'
-import { signupUser } from 'pubsweet-component-signup/actions'
+import { compose } from 'recompose'
+import { reduxForm, SubmissionError } from 'redux-form'
+import { signup } from '../redux/signup'
 import Signup from './Signup'
 
+const onSubmit = (values, dispatch) => {
+  dispatch(signup(values)).catch(error => {
+    if (error.validationErrors) {
+      throw new SubmissionError(error.validationErrors)
+    } else {
+      //
+    }
+  })
+}
+
 export default compose(
   reduxForm({
-    form: 'signup'
+    form: 'signup',
+    onSubmit
   }),
   connect(
     state => ({
-      signupError: state.error
-    }),
-    {
-      onSubmit: signupUser
-    }
+      error: state.signup.error
+    })
   )
 )(Signup)
diff --git a/packages/component-authentication/src/index.js b/packages/component-authentication/src/index.js
index 243220c28587a64c83710316cdaa8ec04ba8495d..c75b39da150fbfbf63739f313d084648a3416a71 100644
--- a/packages/component-authentication/src/index.js
+++ b/packages/component-authentication/src/index.js
@@ -2,6 +2,11 @@ module.exports = {
   frontend: {
     components: [
       () => require('./components')
-    ]
+    ],
+    reducers: {
+      login: () => require('./redux/login'),
+      signup: () => require('./redux/signup'),
+      currentUser: () => require('./redux/currentUser')
+    }
   },
 }
diff --git a/packages/component-authentication/src/redux/currentUser.js b/packages/component-authentication/src/redux/currentUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..776b49c7c462e7e6498bad204ee664668f727b4c
--- /dev/null
+++ b/packages/component-authentication/src/redux/currentUser.js
@@ -0,0 +1,87 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+
+import { LOGOUT_SUCCESS } from './logout'
+
+/* constants */
+
+export const GET_CURRENT_USER_REQUEST = 'GET_CURRENT_USER_REQUEST'
+export const GET_CURRENT_USER_SUCCESS = 'GET_CURRENT_USER_SUCCESS'
+export const GET_CURRENT_USER_FAILURE = 'GET_CURRENT_USER_FAILURE'
+
+/* actions */
+
+export const getCurrentUserRequest = () => ({
+  type: GET_CURRENT_USER_REQUEST
+})
+
+export const getCurrentUserSuccess = user => ({
+  type: GET_CURRENT_USER_SUCCESS,
+  user
+})
+
+export const getCurrentUserFailure = error => ({
+  type: GET_CURRENT_USER_FAILURE,
+  error
+})
+
+export const getCurrentUser = () => dispatch => {
+  dispatch(getCurrentUserRequest())
+  return api.get('/users/authenticate').then(
+    user => {
+      dispatch(getCurrentUserSuccess(user))
+    },
+    error => {
+      dispatch(getCurrentUserFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  isAuthenticated: false,
+  user: null,
+  error: null
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case GET_CURRENT_USER_REQUEST:
+      return {
+        isFetching: true,
+        isAuthenticated: false,
+        user: null,
+        error: null,
+      }
+
+    case GET_CURRENT_USER_FAILURE:
+      return {
+        isFetching: false,
+        isAuthenticated: false,
+        user: null,
+        error: action.error
+      }
+
+    case GET_CURRENT_USER_SUCCESS:
+      return {
+        isFetching: false,
+        isAuthenticated: true,
+        user: action.user,
+        error: null
+      }
+
+    // clear the current user on logout
+    case LOGOUT_SUCCESS:
+      return {
+        isFetching: false,
+        isAuthenticated: false,
+        user: null,
+        error: null,
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/component-authentication/src/redux/index.js b/packages/component-authentication/src/redux/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..4434d36dfce249f7b9e605285cbe3322a8107f1e
--- /dev/null
+++ b/packages/component-authentication/src/redux/index.js
@@ -0,0 +1,3 @@
+export * as currentUser from './currentUser'
+export * as login from './login'
+export * as signup from './signup'
diff --git a/packages/component-authentication/src/redux/login.js b/packages/component-authentication/src/redux/login.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0ab01b75a96966b9ee54b3e87f6f1be62860191
--- /dev/null
+++ b/packages/component-authentication/src/redux/login.js
@@ -0,0 +1,75 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+import { push } from 'react-router-redux'
+import { getCurrentUser } from './currentUser'
+
+// TODO: This will break when rendered on a server
+const localStorage = window.localStorage || undefined
+
+/* constants */
+
+export const LOGIN_REQUEST = 'LOGIN_REQUEST'
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
+export const LOGIN_FAILURE = 'LOGIN_FAILURE'
+
+/* actions */
+
+export const loginRequest = credentials => ({
+  type: LOGIN_REQUEST,
+})
+
+export const loginSuccess = user => ({
+  type: LOGIN_SUCCESS,
+})
+
+export const loginFailure = error => ({
+  type: LOGIN_FAILURE,
+  error
+})
+
+export const login = (credentials, redirectTo) => dispatch => {
+  dispatch(loginRequest())
+  return api.create('/users/authenticate', credentials).then(
+    user => {
+      localStorage.setItem('token', user.token)
+      dispatch(loginSuccess())
+      dispatch(getCurrentUser())
+      dispatch(push(redirectTo || '/'))
+    },
+    error => {
+      dispatch(loginFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  error: null
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case LOGIN_REQUEST:
+      return {
+        isFetching: true,
+        error: null
+      }
+
+    case LOGIN_SUCCESS:
+      return {
+        isFetching: false,
+        error: null
+      }
+
+    case LOGIN_FAILURE:
+      return {
+        isFetching: false,
+        error: action.error
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/component-authentication/src/redux/logout.js b/packages/component-authentication/src/redux/logout.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea97faf8fd33cb20c7edad5ef2ba4657fc76fdd8
--- /dev/null
+++ b/packages/component-authentication/src/redux/logout.js
@@ -0,0 +1,17 @@
+import { push } from 'react-router-redux'
+
+/* constants */
+
+export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
+
+/* actions */
+
+export const logoutSuccess = () => ({
+  type: LOGOUT_SUCCESS
+})
+
+export const logout = redirectTo => dispatch => {
+  localStorage.removeItem('token')
+  dispatch(logoutSuccess())
+  if (redirectTo) dispatch(push(redirectTo))
+}
diff --git a/packages/component-authentication/src/redux/signup.js b/packages/component-authentication/src/redux/signup.js
new file mode 100644
index 0000000000000000000000000000000000000000..823a2698766a19de182ff2aba5ba27d53faefc8d
--- /dev/null
+++ b/packages/component-authentication/src/redux/signup.js
@@ -0,0 +1,70 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+import { login } from './login'
+
+/* constants */
+
+export const SIGNUP_REQUEST = 'SIGNUP_REQUEST'
+export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS'
+export const SIGNUP_FAILURE = 'SIGNUP_FAILURE'
+
+/* actions */
+
+export const signupRequest = () => ({
+  type: SIGNUP_REQUEST,
+})
+
+export const signupSuccess = user => ({
+  type: SIGNUP_SUCCESS,
+  user
+})
+
+export const signupFailure = error => ({
+  type: SIGNUP_FAILURE,
+  error
+})
+
+export const signup = credentials => dispatch => {
+  dispatch(signupRequest())
+  return api.create('/users', credentials).then(
+    user => {
+      dispatch(signupSuccess(user))
+      dispatch(login(credentials))
+    },
+    error => {
+      dispatch(signupFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  error: null,
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case SIGNUP_REQUEST:
+      return {
+        isFetching: true,
+        error: null
+      }
+
+    case SIGNUP_SUCCESS:
+      return {
+        isFetching: false,
+        error: null
+      }
+
+    case SIGNUP_FAILURE:
+      return {
+        isFetching: false,
+        error: action.error
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/xpub-collabra/app/routes.js b/packages/xpub-collabra/app/routes.js
index e2e0ed4b8d6f43bd7e84d65d22e945b6373c24e3..7678bccb9f8e1d5899839e33530918732597b5eb 100644
--- a/packages/xpub-collabra/app/routes.js
+++ b/packages/xpub-collabra/app/routes.js
@@ -13,11 +13,11 @@ export default (
     <Redirect from="/" to="/dashboard"/>
 
     <Route path="/" component={App}>
-      {/*<Route component={AuthenticatedPage}>*/}
+      <Route component={AuthenticatedPage}>
         <Route path="dashboard" component={DashboardPage}/>
         <Route path="projects/:project/submit" component={SubmitPage}/>
         <Route path="projects/:project/manuscript" component={ManuscriptPage}/>
-      {/*</Route>*/}
+      </Route>
 
       <Route path="signup" component={SignupPage}/>
       <Route path="login" component={LoginPage}/>
diff --git a/packages/xpub-collabra/config/components.json b/packages/xpub-collabra/config/components.json
index 4e0f0b2f82759fa0d970a6352c6f582a1579002b..9da243271b757ad42f45a7ee918c8305ad30befb 100644
--- a/packages/xpub-collabra/config/components.json
+++ b/packages/xpub-collabra/config/components.json
@@ -1,8 +1,5 @@
 [
-  "pubsweet-component-signup",
-  "pubsweet-component-login",
-  "pubsweet-component-password-reset-frontend",
-  "pubsweet-component-password-reset-backend",
+  "pubsweet-component-xpub-authentication",
   "pubsweet-component-ink-frontend",
   "pubsweet-component-ink-backend"
 ]
diff --git a/packages/xpub-collabra/package.json b/packages/xpub-collabra/package.json
index 161a3f259628685470e898b0b7665542edcfa253..acae52041eb7e39247eec4294b04b05f79669c7c 100644
--- a/packages/xpub-collabra/package.json
+++ b/packages/xpub-collabra/package.json
@@ -17,10 +17,6 @@
     "pubsweet-client": "git+https://gitlab.coko.foundation/pubsweet/pubsweet-client.git",
     "pubsweet-component-ink-backend": "0.0.10",
     "pubsweet-component-ink-frontend": "^0.1.0",
-    "pubsweet-component-login": "^0.3.0",
-    "pubsweet-component-password-reset-backend": "^0.1.0",
-    "pubsweet-component-password-reset-frontend": "^0.1.0",
-    "pubsweet-component-signup": "^0.2.0",
     "pubsweet-component-xpub-app": "^0.0.2",
     "pubsweet-component-xpub-authentication": "^0.0.2",
     "pubsweet-component-xpub-dashboard": "^0.0.2",