diff --git a/lib/base.php b/lib/base.php
index 62ace1bc0efb8c007cc7bfb3caf3d075357616ca..be9de93f73f548ffee19cb6ffe41cc4733088dcb 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -905,7 +905,7 @@ class OC {
 	 * @param OCP\IRequest $request
 	 * @return boolean
 	 */
-	private static function handleLogin(OCP\IRequest $request) {
+	static function handleLogin(OCP\IRequest $request) {
 		$userSession = self::$server->getUserSession();
 		if (OC_User::handleApacheAuth()) {
 			return true;
diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php
index 64179336020e0b375e5b944e240130c0e1977874..eca0051691fc7817ffe18f1344696b90d9078c03 100644
--- a/lib/private/AppFramework/Routing/RouteConfig.php
+++ b/lib/private/AppFramework/Routing/RouteConfig.php
@@ -62,6 +62,61 @@ class RouteConfig {
 
 		// parse resources
 		$this->processResources($this->routes);
+
+		/*
+		 * OCS routes go into a different collection
+		 */
+		$oldCollection = $this->router->getCurrentCollection();
+		$this->router->useCollection($oldCollection.'.ocs');
+
+		// parse ocs simple routes
+		$this->processOCS($this->routes);
+
+		$this->router->useCollection($oldCollection);
+	}
+
+	private function processOCS(array $routes) {
+		$ocsRoutes = isset($routes['ocs']) ? $routes['ocs'] : [];
+		foreach ($ocsRoutes as $ocsRoute) {
+			$name = $ocsRoute['name'];
+			$postfix = '';
+
+			if (isset($ocsRoute['postfix'])) {
+				$postfix = $ocsRoute['postfix'];
+			}
+
+			$url = $ocsRoute['url'];
+			$verb = isset($ocsRoute['verb']) ? strtoupper($ocsRoute['verb']) : 'GET';
+
+			$split = explode('#', $name, 2);
+			if (count($split) != 2) {
+				throw new \UnexpectedValueException('Invalid route name');
+			}
+			$controller = $split[0];
+			$action = $split[1];
+
+			$controllerName = $this->buildControllerName($controller);
+			$actionName = $this->buildActionName($action);
+
+			// register the route
+			$handler = new RouteActionHandler($this->container, $controllerName, $actionName);
+
+			$router = $this->router->create('ocs.'.$this->appName.'.'.$controller.'.'.$action . $postfix, $url)
+				->method($verb)
+				->action($handler);
+
+			// optionally register requirements for route. This is used to
+			// tell the route parser how url parameters should be matched
+			if(array_key_exists('requirements', $ocsRoute)) {
+				$router->requirements($ocsRoute['requirements']);
+			}
+
+			// optionally register defaults for route. This is used to
+			// tell the route parser how url parameters should be default valued
+			if(array_key_exists('defaults', $ocsRoute)) {
+				$router->defaults($ocsRoute['defaults']);
+			}
+		}
 	}
 
 	/**
diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php
index 01262be390c201f4ce962f63d15487247e8499de..f7da827c3db5a8b1bf7c95a76f110fcd12c7761c 100644
--- a/lib/private/Route/Router.php
+++ b/lib/private/Route/Router.php
@@ -150,6 +150,11 @@ class Router implements IRouter {
 				$collection = $this->getCollection($app);
 				$collection->addPrefix('/apps/' . $app);
 				$this->root->addCollection($collection);
+
+				// Also add the OCS collection
+				$collection = $this->getCollection($app.'.ocs');
+				$collection->addPrefix('/ocsapp/apps/' . $app);
+				$this->root->addCollection($collection);
 			}
 		}
 		if (!isset($this->loadedApps['core'])) {
@@ -238,6 +243,13 @@ class Router implements IRouter {
 			// empty string / 'apps' / $app / rest of the route
 			list(, , $app,) = explode('/', $url, 4);
 
+			$app = \OC_App::cleanAppId($app);
+			\OC::$REQUESTEDAPP = $app;
+			$this->loadRoutes($app);
+		} else if (substr($url, 0, 13) === '/ocsapp/apps/') {
+			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
+			list(, , , $app,) = explode('/', $url, 5);
+
 			$app = \OC_App::cleanAppId($app);
 			\OC::$REQUESTEDAPP = $app;
 			$this->loadRoutes($app);
diff --git a/ocs/v1.php b/ocs/v1.php
index bbc2adf39b6ce023b3d104e5ce121109cea5aa22..e3ecefccf29b90a05215c8f3965f343dde436895 100644
--- a/ocs/v1.php
+++ b/ocs/v1.php
@@ -42,6 +42,10 @@ if (\OCP\Util::needUpgrade()
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
 
+/*
+ * Try old routes first
+ * We first try the old routes since the appframework triggers more login stuff.
+ */
 try {
 	OC_App::loadApps(['session']);
 	OC_App::loadApps(['authentication']);
@@ -52,6 +56,24 @@ try {
 	\OC::$server->getL10NFactory()->setLanguageFromRequest();
 
 	OC::$server->getRouter()->match('/ocs'.\OC::$server->getRequest()->getRawPathInfo());
+	return;
+} catch (ResourceNotFoundException $e) {
+	// Fall through the not found
+} catch (MethodNotAllowedException $e) {
+	OC_API::setContentType();
+	OC_Response::setStatus(405);
+} catch (\OC\OCS\Exception $ex) {
+	OC_API::respond($ex->getResult(), OC_API::requestedFormat());
+}
+
+/*
+ * Try the appframework routes
+ */
+try {
+	if(!\OC::$server->getUserSession()->isLoggedIn()) {
+		OC::handleLogin(\OC::$server->getRequest());
+	}
+	OC::$server->getRouter()->match('/ocsapp'.\OC::$server->getRequest()->getRawPathInfo());
 } catch (ResourceNotFoundException $e) {
 	OC_API::setContentType();
 	OC_OCS::notFound();
@@ -60,5 +82,8 @@ try {
 	OC_Response::setStatus(405);
 } catch (\OC\OCS\Exception $ex) {
 	OC_API::respond($ex->getResult(), OC_API::requestedFormat());
+} catch (\Exception $e) {
+	OC_API::setContentType();
+	OC_OCS::notFound();
 }
 
diff --git a/tests/lib/AppFramework/Routing/RoutingTest.php b/tests/lib/AppFramework/Routing/RoutingTest.php
index 52a5eb33ba9e661999926d55b4913e5f7f6ec516..6c8b0f40133b3f20aa9a0c7953a344bc08617e0c 100644
--- a/tests/lib/AppFramework/Routing/RoutingTest.php
+++ b/tests/lib/AppFramework/Routing/RoutingTest.php
@@ -18,6 +18,15 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
 	}
 
+	public function testSimpleOCSRoute() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'GET']
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
+	}
+
 	public function testSimpleRouteWithMissingVerb()
 	{
 		$routes = array('routes' => array(
@@ -27,6 +36,15 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
 	}
 
+	public function testSimpleOCSRouteWithMissingVerb() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open']
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
+	}
+
 	public function testSimpleRouteWithLowercaseVerb()
 	{
 		$routes = array('routes' => array(
@@ -36,6 +54,15 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open');
 	}
 
+	public function testSimpleOCSRouteWithLowercaseVerb() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete']
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open');
+	}
+
 	public function testSimpleRouteWithRequirements()
 	{
 		$routes = array('routes' => array(
@@ -45,6 +72,15 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', array('something'));
 	}
 
+	public function testSimpleOCSRouteWithRequirements() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete', 'requirements' => ['something']]
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', ['something']);
+	}
+
 	public function testSimpleRouteWithDefaults()
 	{
 		$routes = array('routes' => array(
@@ -54,6 +90,16 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', array(), array('param' => 'foobar'));
 	}
 
+
+	public function testSimpleOCSRouteWithDefaults() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete', 'defaults' => ['param' => 'foobar']]
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', [], ['param' => 'foobar']);
+	}
+
 	public function testSimpleRouteWithPostfix()
 	{
 		$routes = array('routes' => array(
@@ -63,6 +109,14 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', array(), array(), '_something');
 	}
 
+	public function testSimpleOCSRouteWithPostfix() {
+		$routes = ['ocs' => [
+				['name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete', 'postfix' => '_something']
+			]
+		];
+
+		$this->assertSimpleOCSRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open', [], [], '_something');
+	}
 
 	/**
 	 * @expectedException \UnexpectedValueException
@@ -86,6 +140,27 @@ class RoutingTest extends \Test\TestCase
 		$config->register();
 	}
 
+	/**
+	 * @expectedException \UnexpectedValueException
+	 */
+	public function testSimpleOCSRouteWithBrokenName() {
+		$routes = ['ocs' => [
+			['name' => 'folders_open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete']
+		]];
+
+		// router mock
+		$router = $this->getMockBuilder('\OC\Route\Router')
+			->setMethods(['create'])
+			->setConstructorArgs([$this->getMockBuilder('\OCP\ILogger')->getMock()])
+			->getMock();
+
+		// load route configuration
+		$container = new DIContainer('app1');
+		$config = new RouteConfig($container, $router, $routes);
+
+		$config->register();
+	}
+
 	public function testSimpleRouteWithUnderScoreNames()
 	{
 		$routes = array('routes' => array(
@@ -95,6 +170,14 @@ class RoutingTest extends \Test\TestCase
 		$this->assertSimpleRoute($routes, 'admin_folders.open_current', 'DELETE', '/folders/{folderId}/open', 'AdminFoldersController', 'openCurrent');
 	}
 
+	public function testSimpleOCSRouteWithUnderScoreNames() {
+		$routes = ['ocs' => [
+			['name' => 'admin_folders#open_current', 'url' => '/folders/{folderId}/open', 'verb' => 'delete']
+		]];
+
+		$this->assertSimpleOCSRoute($routes, 'admin_folders.open_current', 'DELETE', '/folders/{folderId}/open', 'AdminFoldersController', 'openCurrent');
+	}
+
 	public function testResource()
 	{
 		$routes = array('resources' => array('account' => array('url' => '/accounts')));
@@ -145,6 +228,54 @@ class RoutingTest extends \Test\TestCase
 		$config->register();
 	}
 
+	/**
+	 * @param $routes
+	 * @param string $name
+	 * @param string $verb
+	 * @param string $url
+	 * @param string $controllerName
+	 * @param string $actionName
+	 * @param array $requirements
+	 * @param array $defaults
+	 * @param string $postfix
+	 */
+	private function assertSimpleOCSRoute($routes,
+										  $name,
+										  $verb,
+										  $url,
+										  $controllerName,
+										  $actionName,
+										  array $requirements=array(),
+										  array $defaults=array(),
+										  $postfix='')
+	{
+		if ($postfix) {
+			$name .= $postfix;
+		}
+
+		// route mocks
+		$container = new DIContainer('app1');
+		$route = $this->mockRoute($container, $verb, $controllerName, $actionName, $requirements, $defaults);
+
+		// router mock
+		$router = $this->getMockBuilder('\OC\Route\Router')
+			->setMethods(['create'])
+			->setConstructorArgs([$this->getMockBuilder('\OCP\ILogger')->getMock()])
+			->getMock();
+
+		// we expect create to be called once:
+		$router
+			->expects($this->once())
+			->method('create')
+			->with($this->equalTo('ocs.app1.' . $name), $this->equalTo($url))
+			->will($this->returnValue($route));
+
+		// load route configuration
+		$config = new RouteConfig($container, $router, $routes);
+
+		$config->register();
+	}
+
 	/**
 	 * @param string $resourceName
 	 * @param string $url