diff --git a/extra/schema_fill.lua b/extra/schema_fill.lua
index 698c40bd1cd050bd2eadbeb49cbb58a9fae997fd..4259202028f01a34f9d9588a41699d4d90f0f975 100644
--- a/extra/schema_fill.lua
+++ b/extra/schema_fill.lua
@@ -1,6 +1,7 @@
 -- Super User ID
 GUEST = 0
 ADMIN = 1
+PUBLIC = 2
 _schema = box.space[box.schema.SCHEMA_ID]
 _space = box.space[box.schema.SPACE_ID]
 _index = box.space[box.schema.INDEX_ID]
@@ -58,5 +59,6 @@ _index:insert{_cluster.id, 1, 'uuid', 'tree', 1, 1, 1, 'str'}
 
 -- 
 -- Pre-create user and grants
-_user:insert{GUEST, ADMIN, 'guest'}
-_user:insert{ADMIN, ADMIN, 'admin'}
+_user:insert{GUEST, ADMIN, 'guest', 'user'}
+_user:insert{ADMIN, ADMIN, 'admin', 'user'}
+_user:insert{PUBLIC, ADMIN, 'public', 'role'}
diff --git a/src/box/access.cc b/src/box/access.cc
index ca79d99417f4732ab9dd75b604e6798bf4fc893d..d4437c94a16c2047925d796fcdc4b535a8851c5f 100644
--- a/src/box/access.cc
+++ b/src/box/access.cc
@@ -131,7 +131,8 @@ struct user *
 user_by_name(const char *name, uint32_t len)
 {
 	uint32_t uid = schema_find_id(SC_USER_ID, 2, name, len);
-	return user_cache_find(uid);
+	struct user *user = user_cache_find(uid);
+	return user && user->type == SC_USER ? user : NULL;
 }
 
 void
@@ -151,6 +152,7 @@ user_cache_init()
 	memset(&guest, 0, sizeof(guest));
 	snprintf(guest.name, sizeof(guest.name), "guest");
 	guest.owner = ADMIN;
+	guest.type = SC_USER;
 	user_cache_replace(&guest);
 	/* 0 is the auth token and user id by default. */
 	assert(guest.auth_token == GUEST &&
@@ -161,6 +163,7 @@ user_cache_init()
 	memset(&admin, 0, sizeof(admin));
 	snprintf(admin.name, sizeof(admin.name), "admin");
 	admin.uid = admin.owner = ADMIN;
+	admin.type = SC_USER;
 	user_cache_replace(&admin);
 	/* ADMIN is both the auth token and user id for 'admin' user. */
 	assert(admin.auth_token == ADMIN &&
diff --git a/src/box/access.h b/src/box/access.h
index 179c8de591ba5a87b98d183075a849b9efa1f7e8..a101afaf882092bdded6daf893882ee612459a03 100644
--- a/src/box/access.h
+++ b/src/box/access.h
@@ -59,6 +59,8 @@ struct user {
 	uint32_t uid;
 	/** Creator of the user */
 	uint32_t owner;
+	/** 'user' or 'role' */
+	enum schema_object_type type;
 	/** User password - hash2 */
 	char hash2[SCRAMBLE_SIZE];
 	/** User name - for error messages and debugging */
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 647621d4171607ae10d64791292f14b9b3cf99fe..784e6c8948b5df0f3abdd052b68ba8f6566867b7 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -54,7 +54,8 @@
 #define INDEX_PART_COUNT 5
 
 /** _user columns */
-#define AUTH_DATA        3
+#define USER_TYPE        3
+#define AUTH_MECH_LIST   4
 
 /** _priv columns */
 #define PRIV_OBJECT_TYPE 2
@@ -1170,6 +1171,8 @@ user_create_from_tuple(struct user *user, struct tuple *tuple)
 	memset(user, 0, sizeof(*user));
 	user->uid = tuple_field_u32(tuple, ID);
 	user->owner = tuple_field_u32(tuple, UID);
+	const char *user_type = tuple_field_cstr(tuple, USER_TYPE);
+	user->type= schema_object_type(user_type);
 	const char *name = tuple_field_cstr(tuple, NAME);
 	uint32_t len = snprintf(user->name, sizeof(user->name), "%s", name);
 	if (len >= sizeof(user->name)) {
@@ -1184,8 +1187,13 @@ user_create_from_tuple(struct user *user, struct tuple *tuple)
 	 * Check for trivial errors when a plain text
 	 * password is saved in this field instead.
 	 */
-	if (tuple_field_count(tuple) > AUTH_DATA) {
-		const char *auth_data = tuple_field(tuple, AUTH_DATA);
+	if (tuple_field_count(tuple) > AUTH_MECH_LIST) {
+		const char *auth_data = tuple_field(tuple, AUTH_MECH_LIST);
+		if (user->type == SC_ROLE && strlen(auth_data)) {
+			tnt_raise(ClientError, ER_CREATE_USER,
+				  "authentication data can not be set for "
+				  "a role");
+		}
 		user_fill_auth_data(user, auth_data);
 	}
 }
@@ -1235,7 +1243,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_user->owner);
 		/* Can't drop guest or super user */
-		if (uid == GUEST || uid == ADMIN) {
+		if (uid == GUEST || uid == ADMIN || uid == PUBLIC) {
 			tnt_raise(ClientError, ER_DROP_USER,
 				  old_user->name,
 				  "the user is a system user");
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 01826679a3620c1593c8ccb4aee510b82da0835d..2f2f16c8511d20055c95cb475077546cfb78fd45 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index e0f68b910133d7991afdca7e29e38a71e1cdf5a2..66f4fd4f3c9c7904625b7671edae0a91adfe27f8 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -51,9 +51,10 @@ schema_object_type(const char *name)
 	 * here too.
 	 */
 	static const char *strs[] = {
-		"unknown", "universe", "space", "function" };
-	int index = strindex(strs, name, 4);
-	return (enum schema_object_type) (index == 4 ? 0 : index);
+		"unknown", "universe", "space", "function", "user", "role" };
+	int n_strs = sizeof(strs)/sizeof(*strs);
+	int index = strindex(strs, name, n_strs);
+	return (enum schema_object_type) (index == n_strs ? 0 : index);
 }
 
 struct key_def *
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 30826ce21aeabef39d0d707e8f895200695f352f..bdbd9d87a4a4b0051cbbfafdb574c9d30bc1e7cc 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -62,7 +62,8 @@ enum {
  * even when there are more object types in the future.
  */
 enum schema_object_type {
-	SC_UNKNOWN = 0, SC_UNIVERSE = 1, SC_SPACE = 2, SC_FUNCTION = 3
+	SC_UNKNOWN = 0, SC_UNIVERSE = 1, SC_SPACE = 2, SC_FUNCTION = 3,
+	SC_USER = 4, SC_ROLE = 5
 };
 
 enum schema_object_type
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 77f964344de2d5098ca85881fc2683a0e0e6e28c..a4a62510d170242e4c0f57d6193ab3e5fff0e978 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -650,6 +650,22 @@ local function object_resolve(object_type, object_name)
                       "Function '"..object_name.."' does not exist")
         end
     end
+    if object_type == 'role' then
+        local _user = box.space[box.schema.USER_ID]
+        local role
+        if type(object_name) == 'string' then
+            role = _user.index['name']:get{object_name}
+        else
+            role = _user.index['primary']:get{object_name}
+        end
+        if role then
+            return role[1]
+        else
+            box.raise(box.error.NO_SUCH_USER,
+                      "Role '"..object_name.."' does not exist")
+        end
+    end
+
     box.raise(box.error.UNKNOWN_SCHEMA_OBJECT,
               "Unknown object type '"..object_type.."'")
 end
@@ -692,7 +708,7 @@ box.schema.user.passwd = function(new_password)
     local _user = box.space[box.schema.USER_ID]
     auth_mech_list = {}
     auth_mech_list["chap-sha1"] = box.schema.user.password(new_password)
-    _user:update({uid}, {"=", 3, auth_mech_list})
+    _user:update({uid}, {"=", 4, auth_mech_list})
 end
 
 box.schema.user.create = function(name, opts)
@@ -709,7 +725,7 @@ box.schema.user.create = function(name, opts)
         auth_mech_list["chap-sha1"] = box.schema.user.password(opts.password)
     end
     local _user = box.space[box.schema.USER_ID]
-    _user:auto_increment{session.uid(), name, auth_mech_list}
+    _user:auto_increment{session.uid(), name, 'user', auth_mech_list}
 end
 
 box.schema.user.drop = function(name)
@@ -775,3 +791,19 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name
     end
 end
 
+box.schema.role = {}
+
+box.schema.role.create = function(name)
+    local uid = user_resolve(name)
+    if uid then
+        box.raise(box.error.USER_EXISTS,
+                  "Role '"..name.."' already exists")
+    end
+    local _user = box.space[box.schema.USER_ID]
+    _user:auto_increment{session.uid(), name, 'role'}
+end
+
+box.schema.role.drop = function(name)
+    return box.schema.user.drop(name)
+end
+
diff --git a/src/session.h b/src/session.h
index b2efcbe95998efa7effc0ac04a2fa8d4375ca398..ab0e620ba1311d7bace4338913799f57479056b4 100644
--- a/src/session.h
+++ b/src/session.h
@@ -34,7 +34,7 @@
 
 enum {	SESSION_SEED_SIZE = 32, SESSION_DELIM_SIZE = 16 };
 /** Predefined user ids. */
-enum { GUEST = 0, ADMIN =  1 };
+enum { GUEST = 0, ADMIN =  1, PUBLIC = 2 /* role */ };
 
 /**
  * Abstraction of a single user session:
diff --git a/test/box/access.result b/test/box/access.result
index adc661c7b16e79d212433fcc7953b7ceaef1d927..dc7d676afed5ba6e7def0f015e0c770aa59c2335 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -119,7 +119,7 @@ box.schema.user.revoke('rich', 'read,write', 'universe')
 ...
 box.space['_user']:delete{uid}
 ---
-- [3, 1, 'rich', []]
+- [3, 1, 'rich', 'user', []]
 ...
 box.schema.user.drop('test')
 ---
diff --git a/test/box/auth_access.result b/test/box/auth_access.result
index dfef0f528db76ecd2a9d4191f6515ffd0f04fedf..83c9802e3d40303cd3eadf8b99d5df66bb42e9b6 100644
--- a/test/box/auth_access.result
+++ b/test/box/auth_access.result
@@ -247,7 +247,7 @@ box.space.admin_space:select()
 ...
 box.space._user:select(1)
 ---
-- - [1, 1, 'admin']
+- - [1, 1, 'admin', 'user']
 ...
 box.space._space:select(280)
 ---
@@ -332,20 +332,20 @@ box.space._user:select(1)
 ---
 - error: Read access denied for user 'testuser' to space '_user'
 ...
-box.space._user:insert{3, session.uid(), 'someone'}
+box.space._user:insert{3, session.uid(), 'someone', 'user'}
 ---
-- [3, 2, 'someone']
+- [3, 2, 'someone', 'user']
 ...
 box.space._user:delete(3)
 ---
-- [3, 2, 'someone']
+- [3, 2, 'someone', 'user']
 ...
 session.su('admin')
 ---
 ...
 box.space._user:select(1)
 ---
-- - [1, 1, 'admin']
+- - [1, 1, 'admin', 'user']
 ...
 box.space._user:delete(3)
 ---
@@ -368,9 +368,9 @@ box.space._user:delete(2)
 ...
 box.space._user:select(1)
 ---
-- - [1, 1, 'admin']
+- - [1, 1, 'admin', 'user']
 ...
-box.space._user:insert{4,'','someone2'}
+box.space._user:insert{4,session.uid(),'someone2', 'user'}
 ---
 - error: Write access denied for user 'testuser' to space '_user'
 ...
@@ -555,9 +555,9 @@ s:drop()
 ...
 box.space._user:select()
 ---
-- - [0, 1, 'guest']
-  - [1, 1, 'admin']
-  - [2, 1, 'testuser', []]
+- - [0, 1, 'guest', 'user']
+  - [1, 1, 'admin', 'user']
+  - [2, 1, 'testuser', 'user', []]
 ...
 box.space._space:select()
 ---
diff --git a/test/box/auth_access.test.lua b/test/box/auth_access.test.lua
index 55b12055e7c212bd6189ddee9ee13f909bb4cc7e..b0e397e95fe008c7cdfd8e36facd13b8f57c856f 100644
--- a/test/box/auth_access.test.lua
+++ b/test/box/auth_access.test.lua
@@ -139,7 +139,7 @@ box.schema.user.grant('testuser', 'write', 'space', '_user')
 session.su('testuser')
 box.space._user:delete(2)
 box.space._user:select(1)
-box.space._user:insert{3, session.uid(), 'someone'}
+box.space._user:insert{3, session.uid(), 'someone', 'user'}
 box.space._user:delete(3)
 
 session.su('admin')
@@ -153,7 +153,7 @@ box.schema.user.grant('testuser', 'read', 'space', '_user')
 session.su('testuser')
 box.space._user:delete(2)
 box.space._user:select(1)
-box.space._user:insert{4,'','someone2'}
+box.space._user:insert{4,session.uid(),'someone2', 'user'}
 
 session.su('admin')
 --