diff --git a/js/ui/status/location.js b/js/ui/status/location.js
index 1aae47a3c..59cd04bfd 100644
--- a/js/ui/status/location.js
+++ b/js/ui/status/location.js
@@ -7,12 +7,16 @@ const Lang = imports.lang;
 const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const ModalDialog = imports.ui.modalDialog;
 const Shell = imports.gi.Shell;
+const St = imports.gi.St;
 
 const LOCATION_SCHEMA = 'org.gnome.system.location';
 const MAX_ACCURACY_LEVEL = 'max-accuracy-level';
 const ENABLED = 'enabled';
-const APPS = 'applications';
+
+const APP_PERMISSIONS_TABLE = 'desktop';
+const APP_PERMISSIONS_ID = 'geolocation';
 
 const GeoclueAccuracyLevel = {
     NONE: 0,
@@ -47,6 +51,26 @@ var AgentIface = '<node> \
   </interface> \
 </node>';
 
+var XdgAppIface = '<node> \
+  <interface name="org.freedesktop.XdgApp.PermissionStore"> \
+    <method name="Lookup"> \
+      <arg name="table" type="s" direction="in"/> \
+      <arg name="id" type="s" direction="in"/> \
+      <arg name="permissions" type="a{sas}" direction="out"/> \
+      <arg name="data" type="v" direction="out"/> \
+    </method> \
+    <method name="Set"> \
+      <arg name="table" type="s" direction="in"/> \
+      <arg name="create" type="b" direction="in"/> \
+      <arg name="id" type="s" direction="in"/> \
+      <arg name="app_permissions" type="a{sas}" direction="in"/> \
+      <arg name="data" type="v" direction="in"/> \
+    </method> \
+  </interface> \
+</node>';
+
+const PermissionStore = Gio.DBusProxy.makeProxyWrapper(XdgAppIface);
+
 const Indicator = new Lang.Class({
     Name: 'LocationIndicator',
     Extends: PanelMenu.SystemIndicator,
@@ -84,73 +108,200 @@ const Indicator = new Lang.Class({
         this._onSessionUpdated();
         this._onMaxAccuracyLevelChanged();
         this._connectToGeoclue();
+        this._connectToPermissionStore();
     },
 
     get MaxAccuracyLevel() {
         return this._getMaxAccuracyLevel();
     },
 
-    AuthorizeApp: function(desktop_id, reqAccuracyLevel) {
-        let apps = this._settings.get_value(APPS);
-        let nApps = apps.n_children();
+    AuthorizeAppAsync: function(params, invocation) {
+        let [desktop_id, reqAccuracyLevel] = params;
+        log("%s is requesting location".format(desktop_id));
 
-        for (let i = 0; i < nApps; i++) {
-            let [app, levelStr] = apps.get_child_value(i).deep_unpack();
-            if (app != desktop_id)
-                continue;
+        let callback = function(level, timesAllowed) {
+            if (level >= GeoclueAccuracyLevel.NONE && timesAllowed > 2) {
+                log("%s is in store".format(desktop_id));
+                let accuracyLevel = clamp(reqAccuracyLevel, 0, level);
+                this._completeAuthorizeApp(desktop_id,
+                                           accuracyLevel,
+                                           timesAllowed,
+                                           invocation);
+            } else {
+                log("%s not in store".format(desktop_id));
+                this._userAuthorizeApp(desktop_id,
+                                       reqAccuracyLevel,
+                                       timesAllowed,
+                                       invocation);
+            }
+        }
+        this._fetchPermissionFromStore(desktop_id, Lang.bind(this, callback);
+    },
 
-            level = GeoclueAccuracyLevel[levelStr.toUpperCase()] ||
-                    GeoclueAccuracyLevel.NONE;
-            if (level == GeoclueAccuracyLevel.NONE)
-                return [false, 0];
-
-            let allowedAccuracyLevel = clamp(reqAccuracyLevel, 0, level);
-            allowedAccuracyLevel = clamp(allowedAccuracyLevel, 0, this._getMaxAccuracyLevel());
-
-            return [true, allowedAccuracyLevel];
+    _userAuthorizeApp: function(desktopId, reqAccuracyLevel, timesAllowed, invocation) {
+        var appSystem = Shell.AppSystem.get_default();
+        var app = appSystem.lookup_app(desktopId + ".desktop");
+        if (app == null) {
+            this._completeAuthorizeApp(desktopId,
+                                       GeoclueAccuracyLevel.NONE,
+                                       timesAllowed,
+                                       invocation);
+            return;
         }
 
-        return [false, 0];
+        var name = app.get_name();
+        var icon = app.get_app_info().get_icon();
+        var reason = app.get_string("X-Geoclue-Reason");
+        var allowCallback = function() {
+            this._completeAuthorizeApp(desktopId,
+                                       reqAccuracyLevel,
+                                       timesAllowed,
+                                       invocation);
+        };
+        var denyCallback = function() {
+            this._completeAuthorizeApp(desktopId,
+                                       GeoclueAccuracyLevel.NONE,
+                                       timesAllowed,
+                                       invocation);
+        };
+
+        this._showAppAuthDialog(name,
+                                reason,
+                                icon,
+                                Lang.bind(this, allowCallback),
+                                Lang.bind(this, denyCallback));
+    },
+
+    _showAppAuthDialog: function(name, reason, icon, allowCallback, denyCallback) {
+        if (this._dialog == null)
+            this._dialog = new GeolocationDialog(name, reason, icon);
+        else
+            this._dialog.update(name, reason, icon);
+
+        let closedId = this._dialog.connect('closed', function() {
+            this._dialog.disconnect(closedId);
+            if (this._dialog.allowed)
+                allowCallback ();
+            else
+                denyCallback ();
+        }.bind(this));
+
+        this._dialog.open(global.get_current_time ());
+    },
+
+    _completeAuthorizeApp: function(desktopId,
+                                    accuracyLevel,
+                                    timesAllowed,
+                                    invocation) {
+        if (accuracyLevel == GeoclueAccuracyLevel.NONE) {
+            invocation.return_value(GLib.Variant.new('(bu)',
+                                                     [false, accuracyLevel]));
+            this._saveToPermissionStore(desktopId, accuracyLevel, 0);
+            return;
+        }
+
+        let allowedAccuracyLevel = clamp(accuracyLevel,
+                                         0,
+                                         this._getMaxAccuracyLevel());
+        invocation.return_value(GLib.Variant.new('(bu)',
+                                                 [true, allowedAccuracyLevel]));
+
+        this._saveToPermissionStore(desktopId, allowedAccuracyLevel, timesAllowed + 1);
+    },
+
+    _fetchPermissionFromStore: function(desktopId, callback) {
+        if (this._permStoreProxy == null) {
+            callback (-1, 0);
+
+            return;
+        }
+
+        this._permStoreProxy.LookupRemote(APP_PERMISSIONS_TABLE,
+                                          APP_PERMISSIONS_ID,
+                                          function(result, error) {
+            if (error != null) {
+                log(error.message);
+                callback(-1, 0);
+
+                return;
+            }
+
+            let [permissions, data] = result;
+            let permission = permissions[desktopId];
+            if (permission == null) {
+                callback(-1, 0);
+
+                return;
+            }
+
+            let levelStr = permission[0];
+            let level = GeoclueAccuracyLevel[levelStr.toUpperCase()] ||
+                        GeoclueAccuracyLevel.NONE;
+            let timesAllowed = data.get_byte();
+
+            callback(level, timesAllowed);
+        });
+    },
+
+    _saveToPermissionStore: function(desktopId,
+                                     allowedAccuracyLevel,
+                                     timesAllowed) {
+        if (timesAllowed > 2 || this._permStoreProxy == null)
+            return;
+
+        let levelStr = Object.keys(GeoclueAccuracyLevel)[allowedAccuracyLevel]; 
+        let permission = { desktopId: [levelStr] };
+        let permissions = GLib.Variant.new('a{sas}', [permission]));
+        let data = GLib.Variant.new('y', timesAllowed);
+
+        this._permStoreProxy.SetRemote(APP_PERMISSIONS_TABLE,
+                                       true,
+                                       APP_PERMISSIONS_ID,
+                                       permissions,
+                                       data,
+                                       function (result, error) {
+            log(error.message);
+        });
     },
 
     _syncIndicator: function() {
-        if (this._proxy == null) {
+        if (this._managerProxy == null) {
             this._indicator.visible = false;
             this._item.actor.visible = false;
             return;
         }
 
-        this._indicator.visible = this._proxy.InUse;
+        this._indicator.visible = this._managerProxy.InUse;
         this._item.actor.visible = this._indicator.visible;
         this._updateMenuLabels();
     },
 
     _connectToGeoclue: function() {
-        if (this._proxy != null || this._connecting)
+        if (this._managerProxy != null || this._connecting)
             return false;
 
         this._connecting = true;
         new GeoclueManager(Gio.DBus.system,
                            'org.freedesktop.GeoClue2',
                            '/org/freedesktop/GeoClue2/Manager',
-                           Lang.bind(this, this._onProxyReady));
+                           Lang.bind(this, this._onManagerProxyReady));
         return true;
     },
 
-    _onProxyReady: function(proxy, error) {
+    _onManagerProxyReady: function(proxy, error) {
         if (error != null) {
             log(error.message);
             this._connecting = false;
             return;
         }
 
-        this._proxy = proxy;
-        this._propertiesChangedId = this._proxy.connect('g-properties-changed',
+        this._managerProxy = proxy;
+        this._propertiesChangedId = this._managerProxy.connect('g-properties-changed',
                                                         Lang.bind(this, this._onGeocluePropsChanged));
 
         this._syncIndicator();
 
-        this._proxy.AddAgentRemote('gnome-shell', Lang.bind(this, this._onAgentRegistered));
+        this._managerProxy.AddAgentRemote('gnome-shell', Lang.bind(this, this._onAgentRegistered));
     },
 
     _onAgentRegistered: function(result, error) {
@@ -163,10 +314,10 @@ const Indicator = new Lang.Class({
 
     _onGeoclueVanished: function() {
         if (this._propertiesChangedId) {
-            this._proxy.disconnect(this._propertiesChangedId);
+            this._managerProxy.disconnect(this._propertiesChangedId);
             this._propertiesChangedId = 0;
         }
-        this._proxy = null;
+        this._managerProxy = null;
 
         this._syncIndicator();
     },
@@ -221,9 +372,85 @@ const Indicator = new Lang.Class({
         let unpacked = properties.deep_unpack();
         if ("InUse" in unpacked)
             this._syncIndicator();
-    }
+    },
+
+    _connectToPermissionStore: function() {
+        this._permStoreProxy = null;
+        new PermissionStore(Gio.DBus.session,
+                           'org.freedesktop.XdgApp',
+                           '/org/freedesktop/XdgApp/PermissionStore',
+                           Lang.bind(this, this._onPermStoreProxyReady));
+    },
+
+    _onPermStoreProxyReady: function(proxy, error) {
+        if (error != null) {
+            log(error.message);
+            return;
+        }
+
+        this._permStoreProxy = proxy;
+    },
 });
 
 function clamp(value, min, max) {
     return Math.max(min, Math.min(max, value));
 }
+
+const GeolocationDialog = new Lang.Class({
+    Name: 'GeolocationDialog',
+    Extends: ModalDialog.ModalDialog,
+
+    // FIXME: Would be nice to show the application icon too
+    _init: function(name, reason, icon) {
+        this.parent({ destroyOnClose: false });
+
+        let text = _("'%s' is requesting access to location data.").format (name);
+        this._label = new St.Label({ style_class: 'prompt-dialog-description',
+                                     text: text });
+
+        this.contentLayout.add(this._label, {});
+
+        if (reason != null) {
+            this._reasonLabel = new St.Label({ style_class: 'prompt-dialog-description',
+                                               text: reason });
+            this.contentLayout.add(this._reasonLabel, {});
+        } else {
+            this._reasonLabel = null;
+        }
+
+        this._allowButton = this.addButton({ label: _("Confirm"),
+                                             action: this._onAllowClicked.bind(this),
+                                             default: false },
+                                           { expand: true, x_fill: false, x_align: St.Align.END });
+        this._denyButton = this.addButton({ label: _("Cancel"),
+                                            action: this._onDisallowClicked.bind(this),
+                                            default: true },
+                                          { expand: true, x_fill: false, x_align: St.Align.START });
+    },
+
+    update: function(name, reason, icon) {
+        let text = _("'%s' is requesting access to location data.").format (name);
+        this._label.text = text;
+
+        if (this._reasonLabel != null) {
+            this.contentLayout.remove(this._reasonLabel, {});
+            this._reasonLabel = null;
+        }
+
+        if (reason != null) {
+            this._reasonLabel = new St.Label({ style_class: 'prompt-dialog-description',
+                                               text: reason });
+            this.contentLayout.add(this._reasonLabel, {});
+        }
+    },
+
+    _onAllowClicked: function() {
+        this.allowed = true;
+        this.close();
+    },
+
+    _onDisallowClicked: function() {
+        this.allowed = false;
+        this.close();
+    }
+});