diff --git a/spec/Auth.spec.js b/spec/Auth.spec.js
index a4d97639f0..81aacb01aa 100644
--- a/spec/Auth.spec.js
+++ b/spec/Auth.spec.js
@@ -1,6 +1,6 @@
 describe('Auth', () => {
-  const Auth = require('../lib/Auth.js').Auth;
-
+  const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
+  const Config = require('../lib/Config');
   describe('getUserRoles', () => {
     let auth;
     let config;
@@ -90,4 +90,33 @@ describe('Auth', () => {
     });
 
   });
+
+  it('should load auth without a config', async () => {
+    const user = new Parse.User();
+    await user.signUp({
+      username: 'hello',
+      password: 'password'
+    });
+    expect(user.getSessionToken()).not.toBeUndefined();
+    const userAuth = await getAuthForSessionToken({
+      sessionToken: user.getSessionToken()
+    });
+    expect(userAuth.user instanceof Parse.User).toBe(true);
+    expect(userAuth.user.id).toBe(user.id);
+  });
+
+  it('should load auth with a config', async () => {
+    const user = new Parse.User();
+    await user.signUp({
+      username: 'hello',
+      password: 'password'
+    });
+    expect(user.getSessionToken()).not.toBeUndefined();
+    const userAuth = await getAuthForSessionToken({
+      sessionToken: user.getSessionToken(),
+      config: Config.get('test'),
+    });
+    expect(userAuth.user instanceof Parse.User).toBe(true);
+    expect(userAuth.user.id).toBe(user.id);
+  });
 });
diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js
index 4d52bbe369..ba8956a684 100644
--- a/spec/CloudCode.spec.js
+++ b/spec/CloudCode.spec.js
@@ -832,10 +832,7 @@ describe('Cloud Code', () => {
         expect(body.result).toEqual('second data');
         done();
       })
-      .catch(error => {
-        fail(JSON.stringify(error));
-        done();
-      });
+      .catch(done.fail);
   });
 
   it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => {
diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js
index 19f34bb447..283f1cafaa 100644
--- a/spec/ParseRole.spec.js
+++ b/spec/ParseRole.spec.js
@@ -142,7 +142,7 @@ describe('Parse Role testing', () => {
 
   });
 
-  it("should recursively load roles", (done) => {
+  function testLoadRoles(config, done) {
     const rolesNames = ["FooRole", "BarRole", "BazRole"];
     const roleIds = {};
     createTestUser().then((user) => {
@@ -159,7 +159,7 @@ describe('Parse Role testing', () => {
         return createRole(rolesNames[2], anotherRole, null);
       }).then((lastRole) => {
         roleIds[lastRole.get("name")] = lastRole.id;
-        const auth = new Auth({ config: Config.get("test"), isMaster: true, user: user });
+        const auth = new Auth({ config, isMaster: true, user: user });
         return auth._loadRoles();
       })
     }).then((roles) => {
@@ -172,6 +172,14 @@ describe('Parse Role testing', () => {
       fail("should succeed")
       done();
     });
+  }
+
+  it("should recursively load roles", (done) => {
+    testLoadRoles(Config.get('test'), done);
+  });
+
+  it("should recursively load roles without config", (done) => {
+    testLoadRoles(undefined, done);
   });
 
   it("_Role object should not save without name.", (done) => {
diff --git a/src/Auth.js b/src/Auth.js
index 8658f13025..c4ec748b7a 100644
--- a/src/Auth.js
+++ b/src/Auth.js
@@ -5,8 +5,9 @@ const Parse = require('parse/node');
 // An Auth object tells you who is requesting something and whether
 // the master key was used.
 // userObject is a Parse.User and can be null if there's no user.
-function Auth({ config, isMaster = false, isReadOnly = false, user, installationId } = {}) {
+function Auth({ config, cacheController = undefined, isMaster = false, isReadOnly = false, user, installationId }) {
   this.config = config;
+  this.cacheController = cacheController || (config && config.cacheController);
   this.installationId = installationId;
   this.isMaster = isMaster;
   this.user = user;
@@ -48,47 +49,58 @@ function nobody(config) {
 
 
 // Returns a promise that resolves to an Auth object
-var getAuthForSessionToken = function({ config, sessionToken, installationId } = {}) {
-  return config.cacheController.user.get(sessionToken).then((userJSON) => {
+const getAuthForSessionToken = async function({ config, cacheController, sessionToken, installationId }) {
+  cacheController = cacheController || (config && config.cacheController);
+  if (cacheController) {
+    const userJSON = await cacheController.user.get(sessionToken);
     if (userJSON) {
       const cachedUser = Parse.Object.fromJSON(userJSON);
-      return Promise.resolve(new Auth({config, isMaster: false, installationId, user: cachedUser}));
+      return Promise.resolve(new Auth({config, cacheController, isMaster: false, installationId, user: cachedUser}));
     }
+  }
 
-    var restOptions = {
+  let results;
+  if (config) {
+    const restOptions = {
       limit: 1,
       include: 'user'
     };
 
-    var query = new RestQuery(config, master(config), '_Session', {sessionToken}, restOptions);
-    return query.execute().then((response) => {
-      var results = response.results;
-      if (results.length !== 1 || !results[0]['user']) {
-        throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
-      }
+    const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
+    results = (await query.execute()).results;
+  } else {
+    results = (await new Parse.Query(Parse.Session)
+      .limit(1)
+      .include('user')
+      .equalTo('sessionToken', sessionToken)
+      .find({ useMasterKey: true })).map((obj) => obj.toJSON())
+  }
 
-      var now = new Date(),
-        expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined;
-      if (expiresAt < now) {
-        throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
-          'Session token is expired.');
-      }
-      var obj = results[0]['user'];
-      delete obj.password;
-      obj['className'] = '_User';
-      obj['sessionToken'] = sessionToken;
-      config.cacheController.user.put(sessionToken, obj);
-      const userObject = Parse.Object.fromJSON(obj);
-      return new Auth({config, isMaster: false, installationId, user: userObject});
-    });
-  });
+  if (results.length !== 1 || !results[0]['user']) {
+    throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
+  }
+  const now = new Date(),
+    expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined;
+  if (expiresAt < now) {
+    throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
+      'Session token is expired.');
+  }
+  const obj = results[0]['user'];
+  delete obj.password;
+  obj['className'] = '_User';
+  obj['sessionToken'] = sessionToken;
+  if (cacheController) {
+    cacheController.user.put(sessionToken, obj);
+  }
+  const userObject = Parse.Object.fromJSON(obj);
+  return new Auth({ config, cacheController, isMaster: false, installationId, user: userObject });
 };
 
-var getAuthForLegacySessionToken = function({config, sessionToken, installationId } = {}) {
+var getAuthForLegacySessionToken = function({ config, sessionToken, installationId }) {
   var restOptions = {
     limit: 1
   };
-  var query = new RestQuery(config, master(config), '_User', { sessionToken: sessionToken}, restOptions);
+  var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
   return query.execute().then((response) => {
     var results = response.results;
     if (results.length !== 1) {
@@ -97,7 +109,7 @@ var getAuthForLegacySessionToken = function({config, sessionToken, installationI
     const obj = results[0];
     obj.className = '_User';
     const userObject = Parse.Object.fromJSON(obj);
-    return new Auth({config, isMaster: false, installationId, user: userObject});
+    return new Auth({ config, isMaster: false, installationId, user: userObject });
   });
 }
 
@@ -116,84 +128,113 @@ Auth.prototype.getUserRoles = function() {
   return this.rolePromise;
 };
 
-// Iterates through the role tree and compiles a users roles
-Auth.prototype._loadRoles = function() {
-  var cacheAdapter = this.config.cacheController;
-  return cacheAdapter.role.get(this.user.id).then((cachedRoles) => {
-    if (cachedRoles != null) {
-      this.fetchedRoles = true;
-      this.userRoles = cachedRoles;
-      return Promise.resolve(cachedRoles);
-    }
-
-    var restWhere = {
+Auth.prototype.getRolesForUser = function() {
+  if (this.config) {
+    const restWhere = {
       'users': {
         __type: 'Pointer',
         className: '_User',
         objectId: this.user.id
       }
     };
-    // First get the role ids this user is directly a member of
-    var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
-    return query.execute().then((response) => {
-      var results = response.results;
-      if (!results.length) {
-        this.userRoles = [];
-        this.fetchedRoles = true;
-        this.rolePromise = null;
-
-        cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
-        return Promise.resolve(this.userRoles);
-      }
-      var rolesMap = results.reduce((m, r) => {
-        m.names.push(r.name);
-        m.ids.push(r.objectId);
-        return m;
-      }, {ids: [], names: []});
-
-      // run the recursive finding
-      return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names)
-        .then((roleNames) => {
-          this.userRoles = roleNames.map((r) => {
-            return 'role:' + r;
-          });
-          this.fetchedRoles = true;
-          this.rolePromise = null;
-          cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
-          return Promise.resolve(this.userRoles);
-        });
-    });
+    const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
+    return query.execute().then(({ results }) => results);
+  }
+
+  return new Parse.Query(Parse.Role)
+    .equalTo('users', this.user)
+    .find({ useMasterKey: true })
+    .then((results) => results.map((obj) => obj.toJSON()));
+}
+
+// Iterates through the role tree and compiles a user's roles
+Auth.prototype._loadRoles = async function() {
+  if (this.cacheController) {
+    const cachedRoles = await this.cacheController.role.get(this.user.id);
+    if (cachedRoles != null) {
+      this.fetchedRoles = true;
+      this.userRoles = cachedRoles;
+      return cachedRoles;
+    }
+  }
+
+  // First get the role ids this user is directly a member of
+  const results = await this.getRolesForUser();
+  if (!results.length) {
+    this.userRoles = [];
+    this.fetchedRoles = true;
+    this.rolePromise = null;
+
+    this.cacheRoles();
+    return this.userRoles;
+  }
+
+  const rolesMap = results.reduce((m, r) => {
+    m.names.push(r.name);
+    m.ids.push(r.objectId);
+    return m;
+  }, {ids: [], names: []});
+
+  // run the recursive finding
+  const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names);
+  this.userRoles = roleNames.map((r) => {
+    return 'role:' + r;
   });
+  this.fetchedRoles = true;
+  this.rolePromise = null;
+  this.cacheRoles();
+  return this.userRoles;
 };
 
-// Given a list of roleIds, find all the parent roles, returns a promise with all names
-Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) {
-  const ins = roleIDs.filter((roleID) => {
-    return queriedRoles[roleID] !== true;
-  }).map((roleID) => {
-    // mark as queried
-    queriedRoles[roleID] = true;
+Auth.prototype.cacheRoles = function() {
+  if (!this.cacheController) {
+    return false;
+  }
+  this.cacheController.role.put(this.user.id, Array(...this.userRoles));
+  return true;
+}
+
+Auth.prototype.getRolesByIds = function(ins) {
+  const roles = ins.map((id) => {
     return {
       __type: 'Pointer',
       className: '_Role',
-      objectId: roleID
+      objectId: id
     }
   });
+  const restWhere = { 'roles': { '$in': roles }};
+
+  // Build an OR query across all parentRoles
+  if (!this.config) {
+    return new Parse.Query(Parse.Role)
+      .containedIn('roles', ins.map((id) => {
+        const role = new Parse.Object(Parse.Role);
+        role.id = id;
+        return role;
+      }))
+      .find({ useMasterKey: true })
+      .then((results) => results.map((obj) => obj.toJSON()));
+  }
+
+  return new RestQuery(this.config, master(this.config), '_Role', restWhere, {})
+    .execute()
+    .then(({ results }) => results);
+}
+
+// Given a list of roleIds, find all the parent roles, returns a promise with all names
+Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) {
+  const ins = roleIDs.filter((roleID) => {
+    const wasQueried = queriedRoles[roleID] !== true;
+    queriedRoles[roleID] = true;
+    return wasQueried;
+  });
 
   // all roles are accounted for, return the names
   if (ins.length == 0) {
     return Promise.resolve([...new Set(names)]);
   }
-  // Build an OR query across all parentRoles
-  let restWhere;
-  if (ins.length == 1) {
-    restWhere = { 'roles': ins[0] };
-  } else {
-    restWhere = { 'roles': { '$in': ins }}
-  }
-  const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
-  return query.execute().then((response) => {
-    var results = response.results;
+
+  return this.getRolesByIds(ins).then((results) => {
     // Nothing found
     if (!results.length) {
       return Promise.resolve(names);