diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb
index 15ce387387debdeb14a339a13782e746ee6550ff..7c92ecbd79566add8aced1a2c987545f607ccc54 100644
--- a/app/controllers/api/openid_connect/discovery_controller.rb
+++ b/app/controllers/api/openid_connect/discovery_controller.rb
@@ -18,7 +18,7 @@ module Api
           registration_endpoint:                       api_openid_connect_clients_url,
           authorization_endpoint:                      new_api_openid_connect_authorization_url,
           token_endpoint:                              api_openid_connect_access_tokens_url,
-          userinfo_endpoint:                           api_v0_user_url,
+          userinfo_endpoint:                           api_openid_connect_user_info_url,
           jwks_uri:                                    File.join(root_url, "api", "openid_connect", "jwks.json"),
           scopes_supported:                            Api::OpenidConnect::Scope.pluck(:name),
           response_types_supported:                    Api::OpenidConnect::OAuthApplication.available_response_types,
diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb
index 9fb79226d5c0365a936b3861fc465755804cab9e..62de2389bf8c2a90b7b6f4acc4e2549dca0b8905 100644
--- a/app/controllers/api/openid_connect/user_info_controller.rb
+++ b/app/controllers/api/openid_connect/user_info_controller.rb
@@ -8,7 +8,7 @@ module Api
       end
 
       def show
-        render json: current_user, serializer: UserInfoSerializer
+        render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization
       end
 
       def current_user
diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb
index 15becc1f8605eeedf76df8cca7a89a563882c525..2855a38b0aae1f94068dfcf92ce56963e70c2ff2 100644
--- a/app/models/api/openid_connect/id_token.rb
+++ b/app/models/api/openid_connect/id_token.rb
@@ -23,19 +23,29 @@ module Api
       end
 
       def claims
+        sub = build_sub
         @claims ||= {
           iss:       AppConfig.environment.url,
-          # TODO: Convert to proper PPID
-          sub:       "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}",
+          sub:       sub,
           aud:       authorization.o_auth_application.client_id,
           exp:       expires_at.to_i,
           iat:       created_at.to_i,
           auth_time: authorization.user.current_sign_in_at.to_i,
-          nonce:     nonce,
-          acr:       0 # TODO: Adjust ?
+          nonce:     nonce
         }
       end
 
+      def build_sub
+        if authorization.o_auth_application.ppid?
+          sector_identifier = authorization.o_auth_application.sector_identifier_uri
+          pairwise_pseudonymous_identifier =
+            authorization.user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier)
+          pairwise_pseudonymous_identifier.guid
+        else
+          authorization.user.diaspora_handle
+        end
+      end
+
       # TODO: Add support for request objects
     end
   end
diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb
index 3cc2dadbcb6749a3336af7348db460edba7cc3ba..5ecf8ea69ca39244246f98de8f0e84718b908099 100644
--- a/app/models/api/openid_connect/o_auth_application.rb
+++ b/app/models/api/openid_connect/o_auth_application.rb
@@ -8,24 +8,15 @@ module Api
       validates :client_secret, presence: true
       validates :client_name, presence: true
 
-      serialize :redirect_uris, JSON
-      serialize :response_types, JSON
-      serialize :grant_types, JSON
-      serialize :contacts, JSON
+      %i(redirect_uris response_types grant_types contacts).each do |serializable|
+        serialize serializable, JSON
+      end
 
       before_validation :setup, on: :create
 
       def setup
         self.client_id = SecureRandom.hex(16)
         self.client_secret = SecureRandom.hex(32)
-        self.response_types = []
-        self.grant_types = []
-        self.application_type = "web"
-        self.contacts = []
-        self.logo_uri = ""
-        self.client_uri = ""
-        self.policy_uri = ""
-        self.tos_uri = ""
       end
 
       class << self
@@ -46,12 +37,19 @@ module Api
 
         def supported_metadata
           %i(client_name response_types grant_types application_type
-             contacts logo_uri client_uri policy_uri tos_uri redirect_uris)
+             contacts logo_uri client_uri policy_uri tos_uri redirect_uris
+             sector_identifier_uri subject_type)
         end
 
         def registrar_attributes(registrar)
           supported_metadata.each_with_object({}) do |key, attr|
-            attr[key] = registrar.public_send(key) if registrar.public_send(key)
+            value = registrar.public_send(key)
+            next unless value
+            if key == :subject_type
+              attr[:ppid] = (value == "pairwise")
+            else
+              attr[key] = value
+            end
           end
         end
       end
diff --git a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb
new file mode 100644
index 0000000000000000000000000000000000000000..06c115dafeaac6a3cbd671b22ae581048254fdfd
--- /dev/null
+++ b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb
@@ -0,0 +1,22 @@
+module Api
+  module OpenidConnect
+    class PairwisePseudonymousIdentifier < ActiveRecord::Base
+      self.table_name = "ppid"
+
+      belongs_to :o_auth_application
+      belongs_to :user
+
+      validates :user, presence: true
+      validates :sector_identifier, presence: true, uniqueness: {scope: :user}
+      validates :guid, presence: true, uniqueness: true
+
+      before_validation :setup, on: :create
+
+      private
+
+      def setup
+        self.guid = SecureRandom.hex(16)
+      end
+    end
+  end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index a7b58882c2f1f3c1136ff2f1e1623451f15e8d8d..ec009aa473d124053d86260fd98f53834aeee0bb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -76,6 +76,7 @@ class User < ActiveRecord::Base
 
   has_many :reports
 
+  has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier"
   has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
   has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"
 
diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb
index 3f042380de394836930b7589857aa52c6f06c5a2..67cf3db112705dc6512bb5f4497087ca670de14f 100644
--- a/app/serializers/user_info_serializer.rb
+++ b/app/serializers/user_info_serializer.rb
@@ -2,7 +2,15 @@ class UserInfoSerializer < ActiveModel::Serializer
   attributes :sub, :nickname, :profile, :picture, :zoneinfo
 
   def sub
-    object.diaspora_handle # TODO: Change to proper sub
+    auth = serialization_options[:authorization]
+    if auth.o_auth_application.ppid?
+      sector_identifier = auth.o_auth_application.sector_identifier_uri
+      pairwise_pseudonymous_identifier =
+        object.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier)
+      pairwise_pseudonymous_identifier.guid
+    else
+      object.diaspora_handle
+    end
   end
 
   def nickname
diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb
index e9452a6c77f1bc4d081f4ffa6af8512f2b3c2cd8..874122eb2b17314d6b13c5a56f845f6649bfdbbc 100644
--- a/db/migrate/20150613202109_create_o_auth_applications.rb
+++ b/db/migrate/20150613202109_create_o_auth_applications.rb
@@ -5,15 +5,18 @@ class CreateOAuthApplications < ActiveRecord::Migration
       t.string :client_id
       t.string :client_secret
       t.string :client_name
+
       t.string :redirect_uris
       t.string :response_types
       t.string :grant_types
-      t.string :application_type
+      t.string :application_type, default: "web"
       t.string :contacts
       t.string :logo_uri
       t.string :client_uri
       t.string :policy_uri
       t.string :tos_uri
+      t.string :sector_identifier_uri
+      t.boolean :ppid, default: false
 
       t.timestamps null: false
     end
diff --git a/db/migrate/20150708154727_create_scope.rb b/db/migrate/20150708154727_create_scope.rb
index 8d2c51c0282a67ed4cc7ca37937b1b1bbed0f456..afdc320f63ea3ccf301db292407291a6b1b5e651 100644
--- a/db/migrate/20150708154727_create_scope.rb
+++ b/db/migrate/20150708154727_create_scope.rb
@@ -1,7 +1,7 @@
 class CreateScope < ActiveRecord::Migration
   def change
     create_table :scopes do |t|
-      t.string :name
+      t.primary_key :name, :string
 
       t.timestamps null: false
     end
diff --git a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..125b95bfc994174ff6d1053cd9410371838f4390
--- /dev/null
+++ b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb
@@ -0,0 +1,11 @@
+class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration
+  def change
+    create_table :ppid do |t|
+      t.belongs_to :o_auth_application, index: true
+      t.belongs_to :user, index: true
+
+      t.primary_key :guid, :string, limit: 32
+      t.string :sector_identifier
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 21bb99e43b03130da2eea7f43e0579f82ba98323..09519a9e42f88f65cf22e4b0d98684cafab55a5b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20150724152052) do
+ActiveRecord::Schema.define(version: 20150801074555) do
 
   create_table "account_deletions", force: :cascade do |t|
     t.string   "diaspora_handle", limit: 255
@@ -278,21 +278,23 @@ ActiveRecord::Schema.define(version: 20150724152052) do
   add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree
 
   create_table "o_auth_applications", force: :cascade do |t|
-    t.integer  "user_id",          limit: 4
-    t.string   "client_id",        limit: 255
-    t.string   "client_secret",    limit: 255
-    t.string   "client_name",      limit: 255
-    t.string   "redirect_uris",    limit: 255
-    t.string   "response_types",   limit: 255
-    t.string   "grant_types",      limit: 255
-    t.string   "application_type", limit: 255
-    t.string   "contacts",         limit: 255
-    t.string   "logo_uri",         limit: 255
-    t.string   "client_uri",       limit: 255
-    t.string   "policy_uri",       limit: 255
-    t.string   "tos_uri",          limit: 255
-    t.datetime "created_at",                   null: false
-    t.datetime "updated_at",                   null: false
+    t.integer  "user_id",               limit: 4
+    t.string   "client_id",             limit: 255
+    t.string   "client_secret",         limit: 255
+    t.string   "client_name",           limit: 255
+    t.string   "redirect_uris",         limit: 255
+    t.string   "response_types",        limit: 255
+    t.string   "grant_types",           limit: 255
+    t.string   "application_type",      limit: 255, default: "web"
+    t.string   "contacts",              limit: 255
+    t.string   "logo_uri",              limit: 255
+    t.string   "client_uri",            limit: 255
+    t.string   "policy_uri",            limit: 255
+    t.string   "tos_uri",               limit: 255
+    t.string   "sector_identifier_uri", limit: 255
+    t.boolean  "ppid",                              default: false
+    t.datetime "created_at",                                        null: false
+    t.datetime "updated_at",                                        null: false
   end
 
   add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree
@@ -463,6 +465,16 @@ ActiveRecord::Schema.define(version: 20150724152052) do
   add_index "posts", ["tweet_id"], name: "index_posts_on_tweet_id", length: {"tweet_id"=>191}, using: :btree
   add_index "posts", ["type", "pending", "id"], name: "index_posts_on_type_and_pending_and_id", using: :btree
 
+  create_table "ppid", force: :cascade do |t|
+    t.integer "o_auth_application_id", limit: 4
+    t.integer "user_id",               limit: 4
+    t.string  "guid",                  limit: 32
+    t.string  "sector_identifier",     limit: 255
+  end
+
+  add_index "ppid", ["o_auth_application_id"], name: "index_ppid_on_o_auth_application_id", using: :btree
+  add_index "ppid", ["user_id"], name: "index_ppid_on_user_id", using: :btree
+
   create_table "profiles", force: :cascade do |t|
     t.string   "diaspora_handle",  limit: 255
     t.string   "first_name",       limit: 127
diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb
index a34c284d959f6e51505e73ac3616377de7e5d63e..92a1c93d87a36b61e8f9109dc61c9d287c51397e 100644
--- a/features/step_definitions/auth_code_steps.rb
+++ b/features/step_definitions/auth_code_steps.rb
@@ -28,5 +28,9 @@ end
 When /^I parse the tokens and use it obtain user info$/ do
   client_json = JSON.parse(last_response.body)
   access_token = client_json["access_token"]
+  encoded_id_token = client_json["id_token"]
+  decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token,
+                                                                Api::OpenidConnect::IdTokenConfig.public_key
+  expect(decoded_token.sub).to eq(@me.diaspora_handle)
   get api_openid_connect_user_info_path, access_token: access_token
 end
diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb
index 10dee342ea79696831715a25c146d842f424d4e0..4d03351008c406f3f0316c5a123c71de04fffd8f 100644
--- a/lib/account_deleter.rb
+++ b/lib/account_deleter.rb
@@ -47,7 +47,7 @@ class AccountDeleter
   #user deletions
   def normal_ar_user_associates_to_delete
     %i(tag_followings invitations_to_me services aspects user_preferences
-       notifications blocks authorizations o_auth_applications)
+       notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers)
   end
 
   def special_ar_user_associations
diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb
index 5326fa32df9d2a0e0de35ad3159b5b3f3c05d08e..e685aeb475980a300414c3c387934cf384551d30 100644
--- a/spec/controllers/api/openid_connect/clients_controller_spec.rb
+++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb
@@ -6,17 +6,19 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
       it "should return a client id" do
         post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client",
              response_types: [], grant_types: [], application_type: "web", contacts: [],
-             logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client",
-             policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
+             logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
+             policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos",
+             sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise"
         client_json = JSON.parse(response.body)
         expect(client_json["o_auth_application"]["client_id"].length).to eq(32)
+        expect(client_json["o_auth_application"]["ppid"]).to eq(true)
       end
     end
     context "when redirect uri is missing" do
       it "should return a invalid_client_metadata error" do
         post :create, response_types: [], grant_types: [], application_type: "web", contacts: [],
-          logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client",
-          policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
+          logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
+          policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos"
         client_json = JSON.parse(response.body)
         expect(client_json["error"]).to have_content("invalid_client_metadata")
       end
@@ -24,8 +26,9 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
     context "when redirect client_name is missing" do
       it "should return a invalid_client_metadata error" do
         post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [],
-             application_type: "web", contacts: [], logo_uri: "http://test.com/logo.png",
-             client_uri: "http://test.com/client", policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
+             application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png",
+             client_uri: "http://example.com/client", policy_uri: "http://example.com/policy",
+             tos_uri: "http://example.com/tos"
         client_json = JSON.parse(response.body)
         expect(client_json["error"]).to have_content("invalid_client_metadata")
       end
diff --git a/spec/controllers/api/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb
index f71126462a6e60e5ca8580c5090e0776ce6c47e1..0233ae180a98737bc5a3c4c6ec69cbd8feacd8fe 100644
--- a/spec/controllers/api/openid_connect/discovery_controller_spec.rb
+++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb
@@ -18,10 +18,17 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do
   end
 
   describe "#configuration" do
-    it "should have the issuer as the root url" do
+    before do
       get :configuration
+    end
+    it "should have the issuer as the root url" do
       json_body = JSON.parse(response.body)
       expect(json_body["issuer"]).to eq("http://test.host/")
     end
+
+    it "should have the appropriate user info endpoint" do
+      json_body = JSON.parse(response.body)
+      expect(json_body["userinfo_endpoint"]).to eq(api_openid_connect_user_info_url)
+    end
   end
 end
diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb
similarity index 68%
rename from spec/integration/api/users_controller_spec.rb
rename to spec/integration/api/user_info_controller_spec.rb
index 7b13f738fee8f01e1bed34c984aa78656f35b727..ad47b486b528d8650312a7b93d266c3b5c4c6716 100644
--- a/spec/integration/api/users_controller_spec.rb
+++ b/spec/integration/api/user_info_controller_spec.rb
@@ -1,10 +1,12 @@
 require "spec_helper"
 
-describe Api::V0::UsersController do
+describe Api::OpenidConnect::UserInfoController do
   # TODO: Replace with factory
   let!(:client) do
     Api::OpenidConnect::OAuthApplication.create!(
-      client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"])
+      client_name: "Diaspora Test Client",
+      redirect_uris: ["http://localhost:3000/"],
+      ppid: true, sector_identifier_uri: "https://example.com/uri")
   end
   let(:auth_with_read) do
     auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice)
@@ -21,6 +23,9 @@ describe Api::V0::UsersController do
 
     it "shows the info" do
       json_body = JSON.parse(response.body)
+      expected_sub =
+        alice.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid
+      expect(json_body["sub"]).to eq(expected_sub)
       expect(json_body["nickname"]).to eq(alice.name)
       expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s)
     end
diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb
index 69228bcaf93fdac1bd6171815214aa36c7d86c29..d712327d088c1564e306fe339ed872e8a558534f 100644
--- a/spec/lib/api/openid_connect/token_endpoint_spec.rb
+++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb
@@ -3,7 +3,8 @@ require "spec_helper"
 describe Api::OpenidConnect::TokenEndpoint, type: :request do
   let!(:client) do
     Api::OpenidConnect::OAuthApplication.create!(
-      redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client")
+      redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client",
+      ppid: true, sector_identifier_uri: "https://example.com/uri")
   end
   let!(:auth) {
     Api::OpenidConnect::Authorization.find_or_create_by(
@@ -28,6 +29,8 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
         encoded_id_token = json["id_token"]
         decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token,
                                                                       Api::OpenidConnect::IdTokenConfig.public_key
+        expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(sector_identifier: "https://example.com/uri").guid
+        expect(decoded_token.sub).to eq(expected_guid)
         expect(decoded_token.exp).to be > Time.zone.now.utc.to_i
       end