Identity Hub Permissions/Authorization

The success of a decentralized identity platform is dependent upon the ability for users to share their data with other people, organizations, apps, and services in a way that respects and protects a user’s privacy. In our decentralized platform, all user information & data resides in the user’s identity Hub. This document outlines the basic requirements of the authorization layer for identity Hubs and proposes an initial design that meets these requirements.

Contents

Definitions

Hub, User Agent: see supporting docs for an introduction to DIDs.

User, Identity Owner: the DID that owns and manages access to all data in a Hub. This document focuses on a human user as the identity owner, but the identity owner could also be an organization, a device, or otherwise.

Relying Party: the app, service, or organization that consumes a claim or data from a user’s Hub.

Trust Provider: the app, service, or organization that issues a claim or writes data to a user’s Hub.

Schema Provider: an entity, organization, or consortium that publishes a schema for data types used by various applications, e.g. schema.org.

Permission: describes a set of actions that one DID can take on another DID’s data.

Permission Grant: the record that gets created when one DID grants permission to another DID.

Permission Set: a logical grouping of permissions that are frequently used together. The scope of the permissions included in the set should be easily understood by a user in a short phrase.

A primer on authorization

In the context of identity hubs, authorization is the way access to resources (user data) inside a hub is specified and enforced. It can be helpful to distinguish between how authorization is modeled and how authorization is requested. In existing systems, XACML[1] (eXtensible access control markup language) is a good example of a model for access control, while OAuth[2] is the defacto standard framework for requesting access. Wikipedia compares the two nicely:

XACML and OAuth OAuth 2.0 is considered to be an authorization standard. It differs from XACML (eXtensible access control markup language) though in its origin, its purpose, and its applications. OAuth is about:

XACML does not handle user approval or delegated access or password management. XACML simply provides:

XACML and OAuth can be combined together to deliver a more comprehensive approach to authorization. |

We have divided this document into two sections that address each facet of hub authorization:

  1. A basic authorization model that allows users to grant access to their data (analogous to XACML).
  2. An authorization protocol which allows other DIDs to request access from users (analogous to OAuth).

If you’re familiar with XACML, OAuth, or other authorization systems, the following table may help with terminology:

English description OAuth concept XACML concept Identity Hub concept
who is granted access Client Subject Client, Relying Party
who grants the access Resource Owner User, Identity Owner
who holds the resources Resource Server Identity Hub
who manages access to resources Authorization Server User Agent
a logical set of resource and actions Scope PolicySet PermissionSet
the resource being accessed Resource ObjectType
the actions that are allowed Action Verb (CRUD)
the context in which access is allowed Environment

Scope of this document

In scope: use cases & requirements

This proposal is a first cut. The intention is to start simple, and extend the model to include more richness over time. We choose to focus on two simple use cases, described below.

Use case 1: Registering for a website

Alice has added some useful data about her wardrobe style to her Hub: her measurements from her tailor, and a list of her favorite clothing brands. When Alice goes to try out a new online clothing retailer, the retailer’s website allows her to set up an account using her DID. After signing in her DID, the retailer’s website is able to access Alice’s style data. Alice does not have to re-enter her sizes in the site, and the site can give her recommended options based on her brand preferences.

Enabling this use case will require the following from our authorization model:

Use case 2: Reviewing & managing access

Alice learns that one of the websites she visited is making improper use of her personal data. She would like to immediately remove that website’s access to her Hub.

This use case introduces two new requirements:

Out of scope

To be clear, these requirements are not sufficient to consider identity Hubs ready for real world usage. It leaves out several features that have been discussed as being necessary for a minimally viable authorization layer, including[3]:

Features that control what is being granted:

Features that control who is being granted access:

Features that limit/expand where or when access is granted:

Features that control why access is granted:

Features that are related to Hub authorization, but will be addressed at a later time:

Clearly, there is a large body of functionality that can be added to Hub authorization over time. This is why this initial document intentionally strives to be as simple as possible. We’ll incorporate these things into Hub authorization over time as we receive feedback from early adopters of DIDs.

Design goals

In designing Hub authorization, we strive to meet the following design constraints:

  1. Data in a Hub is 100% data type agnostic – the Hub can support any data type that users and participating apps choose. Data is based on standardized schemas & formats, such that many parties across organizations & industries can interact with the data.
  2. Any data in a Hub can be shared with third parties upon consent from the owner.
  3. Users can retain fine-grained control over access to their data.
  4. Data in a Hub can be grouped into logical permissions that users can easily understand.
  5. The set of permissions supported by Hubs should easily expand over time to include new data types, new combinations of data, and new scenarios.
  6. Client applications can request access to data from users by sending requests to users via their user agent.
  7. Client applications can minimize the access they require upon first use, and can request more access over time.
  8. When users are asked to grant access to data, they must be presented with clear, simple, and informative text that helps them understand what access is being requested, by whom, when and for how long.
  9. User agents must protect their users from malicious parties that fool users into divulging their data.

1. Authorization Model

1.1. Data model

The following describes the format of the objects involved in the authorization model.

PermissionGrant

{
  "owner": "did:example:12345", // the identity owner (granters)’s DID
  "grantee": "did:example:67890", // the grantee’s DID
  “object_type”: “https://schemas.clothing.org/measurements”, // the data type
  “allow”: “-R--”, // allows create, read, update, and delete
  ... // additional richness & specificity can be added in the future
}

OPEN: Should a grant allow multiple object types to be included? Why or why not?

OPEN: Should we revert back to using OAuth terminology?

OPEN: What should we name the “grantee” property?

1.2. Granting permissions

When a user grants a permission to another DID, they can do so by specifying the exact objects in the permission grant. When permissions span more than one data type, several PermissionGrant objects can be created. For each PermissionGrant, the following request should be made to the user’s Hub, typically via user agent:

{
  iss: 'did:example:12345', // the user’s DID (user agent sends this request)
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Create',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionGrant'
  },
  payload: [
    {
      data: {
        "@type": 'https://schema.identity.foundation/Hub/PermissionGrant',
        "grantee": "did:example:67890", // the grantee’s DID
        "object_type": "https://schemas.clothing.org/measurements",
        "allow”: "-R--", // allows create, read, update, and delete
      }
    }
  ]
}

Note that the Hub Permissions interface only supports the single PermissionGrant object type. The Hub should reject any requests to create objects of other types in the Permissions interface, barring future updates to the PermissionGrant model.

The response format, and any error conditions, should be consistent with all other requests to Hubs. Upon creation of this permission grant object in a user’s Hub, the permission will be propagated to all other Hub instances listed in the user’s DID document via the Hub’s standard sync & replication protocol. This will ensure that all Hub instances are up-to-date with all new permission grants in a timely manner. For details on the Hub object replication protocol, please refer to this document[5].

1.3. Verifying permissions

The following describes the logic implemented by the Hub’s authorization layer when a request arrives. Example request to Hub from client application

  1. Receive incoming request from client
  2. Determine relevant schema, verb, and client from request
  3. Query for all PermissionGrants that whose object_type matches the schema, for the given client DID
  4. Check if any query results allow the verb in question
  5. Return success/failed authorization check

Note that PermissionGrants do not understand or evaluate the structure of a given schema. For instance, if a user grants access to all “https://schema.org/game” objects, they do not implicitly grant access to all “https://schema.org/videogame” objects (which is a child of game in schema.org’s hierarchy).

1.4. Reviewing & managing permissions

1.4.1. Hub permission management APIs

Get all permissions granted by a user:

// Request
{
  iss: 'did:example:12345', // the user’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionGrant'
  }
}

// Response
{
  '@type': 'Permissions/Read',
  payload: [
    {
      "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
      "grantee": "did:example:67890",
      "object_type": "https://schemas.clothing.org/measurements",
      "allow”: "-R--"
    },
    ...
  ]
}

Get all permissions pertaining to a certain object type in the user’s Hub:

// Request
{
  iss: 'did:example:12345', // the user’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Read',
  request: {
    'https://schema.identity.foundation/Hub/PermissionGrant'
    filters: [{
      'object_type': 'https://schemas.clothing.org/measurements'
    }]
  }
}

// Response
{
  '@type': 'Permissions/Read',
  payload: [
    {
      "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
      "grantee": "did:example:67890",
      "object_type": "https://schemas.clothing.org/measurements",
      "allow”: "-R--"
    },
    ...
  ]
}

Get all permissions pertaining to a certain object in the user’s Hub:

// Request
{
  iss: 'did:example:12345', // the user’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionGrant',
    filters: [{
      'object_type': 'https://schemas.clothing.org/measurements',
      'object_id': '4c3f2aa7-2543-41e1-b839-b16f7c746795'
    }]
  }
}

// Response
{
  '@type': 'Permissions/Read',
  payload: [
    {
      "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
      "grantee": "did:example:67890",
      "object_type": "https://schemas.clothing.org/measurements",
      "allow”: "-R--"
    },
    ...
  ]
}

Get all permissions pertaining to a client DID:

// Request
{
  iss: 'did:example:12345', // the user’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionGrant',
    filters: [{
      'grantee': 'did:example:67890'
    }]
  }
}

// Response
{
  '@type': 'Permissions/Read',
  payload: [
    {
      "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
      "grantee": "did:example:67890",
      "object_type": "https://schemas.clothing.org/measurements",
      "allow”: "-R--"
    },
    ...
  ]
}

OPEN: How do you filter a permissions query down to only certain objects in Hub syntax? Is the above correct?

1.4.2. Permission management experience

Note, all mocks are for illustrative purposes only.

Manage permissions by app/DID Manage permissions by data type

2. Authorization Request Protocol

2.1. Data Model

Our authorization protocol is a minor extension of the DID authentication protocols[6] that standardizes the authorization of data access. It introduces the concept of a PermissionSet, which is analogous to a scope in OAuth. PermissionSets serve three purposes:

  1. They offer a way for client developers to request permission from users to access a related set of objects. We assert that a logical permission to a user will often be comprised of many types of data – not a single data type.
  2. They allow PermissionGrants to be specified by reference in authorization requests, instead of having to include the full details. This reduces the length of authorization requests significantly.
  3. They provide a place from which to serve localized user-friendly strings to be used in consent screens. This makes it easy for user agents to add support for new permissions as they arise, without needing to update their app.
  4. They give developers a re-usable set of permissions to choose from, rather than requiring every developer define their own permissions.

A permission set is defined by the following two objects:

PermissionSet // defines a group of permissions that can be requested by a client

{
  // the set of PermissionGrants that this permission includes
  “permissions”: [ 
    {
      “object_type”: “https://schemas.clothing.org/measurements”
      “allow”: “-R--”,
      ...
    },
    {
      “object_type”: “https://schemas.clothing.org/brandPreferences”
      “verbs”: “-R--”
      ...
    }
  ]

  // the value used in authorization reqeusts
  “name”: “Hub://did:example:abc123/permissions/sets/style/v1.0”,

  // pointer to strings and icons to be used by user agents
  “resourceBundle”: “Hub://did:example:abc123/permissions/sets/style/v1.0”
}
PermissionSetResourceBundle // exposes strings for user agents to use

{
  “language”: “en-us”,

  // displayable text for user agents
  “consent_string_short”: “View your clothing preferences”,
  “consent_string_long”: “Read your sizes...including...and your...favorite brands”

  // a recommended icon to display to users
  “icon”: “/resources/clothing.ico” 
}

OPEN: Should the PermissionSet name above include a hash of the PermissionSet object? That way, should the definition of the PermissionSet change, user agents can take action. Should the hash just replace /style/v1.0 in the name?

2.2. Onboarding new permissions

The process for onboarding a new PermissionSet for use by client developers proceeds as follows.

  1. A developer, an organization, a consortium, or other group of DID ecosystem participants agree upon a set of data format(s) to be used in their respective industry or application. They might publish their schemas, but doing so is not required.
  2. The parties agree upon reasonable permission sets that allow client developers to request access to the data types from users. Each set must contain at least one PermissionGrant, but may contain more. The parties choose a DID used to define their PermissionSets.
  3. Each PermissionSet is then exposed in that DID’s Hub, at the exact location specified by the “name” property of the PermissionSet. PermissionSets are public objects in Hubs, they do not require authentication or authorization to access.
{
  aud: 'did:example:abc123', // the defining DID
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionSet',
    path: '/style/v1.0'
  },
  payload: [
    {
      data: {
        "@type": "https://schema.identity.foundation/Hub/PermissionGrant",
        "permissions": [...]
        "name": "Hub://did:example:abc123/permissions/sets/style/v1.0",
        "resourceBundle”: "..."
      }
    }
  ]
}
  1. The parties then create recommended strings that user agents should use for the set. Each string can be optionally localized into languages relevant to the industry or application. All resources are in the Hub at a location constructed by concatenating the resource bundle from the permission set definition above and a language tag:
{
  aud: 'did:example:abc123', // the defining DID
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionSetResourceBundle',
    path: '/style/v1.0/en-us'
  },
  payload: [
    {
      data: {
        "@type": 'https://schema.identity.foundation/Hub/PermissionSetResourceBundle',
        "language": "en-us",
        "consent_string_short": "View your clothing preferences",
        "consent_string_long": "Read your sizes...including...and your...favorite...",
        “icon”: “/resources/clothing.ico” // a recommended icon to display to users
      }
    }
  ]
}

OPEN: Is the “path” in the above request the right way to model such a query in hub syntax?

  1. The definers of the PermissionSet can then publish developer documentation that describes when and how to use the permission. Developers can use this documentation to decide which PermissionSet(s) minimize the access they request from users.
  2. If a developer should want a new permission set to be created, they can define their own, or work with a trusted entity to add a new PermissionSet to their DID.

2.3. Requesting permissions

2.3.1. Protocol

The following describes the process by which a client application requests permissions from a user via their user agent. This process is an addition to the proposed DID auth protocols[7].

  1. Optionally, a client developer can use a helper SDK to form their permission requests.
  2. The client forms a JWT with the following structure, indicating the desired PermissionSets:
{jwt-header}.
{
  “iss”: “did:example:67890”, // from DID auth spec
  “callback”: ..., // from DID auth spec
  “nonce”: ..., // from DID auth spec
  ...
  “requested”: [ // list of PermissionSets requested, by name
    “Hub://did:example:abc123/permissions/sets/style/v1.0”
  ] 
}
.{jwt-signature}
  1. The client sends the permission request to the user agent via one of the transports described in DID authentication documents. Some examples include:
  2. The client app may present a QR code, which is then scanned by a mobile user agent.
  3. If the client app and user agent are on a single device, it can use an OS custom URI scheme.
  4. If the user’s DID is known ahead of time, the client can send a permission request to the user’s Hub:
{
  iss: 'did:example:67890', // the client’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Action/Create',
  request: {
    type: “https://schema.identity.foundation/Hub/PermissionRequestAction”
  },
  payload: [
    {
      data: {
        “@type”: “https://schema.identity.foundation/Hub/PermissionRequestAction”,
        "request": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...", // the jwt from above
      }
    }
  ]
}

In this case, user agents can then retrieve incoming permission requests from the user’s Hub and present them to the user. After a request is processed, user agents should delete the PermissionRequestAction from the Hub. Note that creating an action in a user’s Hub does not require generally authorization.

  1. The user agent validates the incoming JWT and extracts the client’s DID.
  2. The user agent extracts the requested PermissionSet(s) from the JWT.
  3. The user agent fetches the PermissionSet definition:
{
  aud: 'did:example:abc123', // the defining DID
  '@type': 'Permissions/Read',
  request: {
  type: 'https://schema.identity.foundation/Hub/PermissionSet',
    path: '/style/v1.0'
  },
  payload: [
    {
      data: {
        "@type": "https://schema.identity.foundation/Hub/PermissionGrant",
        "permissions": [...]
        "name": "Hub://did:example:abc123/permissions/sets/style/v1.0",
        "resourceBundle”: "..."
      }
    }
  ]
}
  1. If no matching permission set is found, fail the permission request with an error message (see below).
  2. The user agent loads relevant localized UI strings from its own storage, if they exist for this PermissionSet.
  3. If the user agent doesn’t have its own strings for this PermissionSet, it fetches the recommended strings:
{
  aud: 'did:example:abc123',
  '@type': 'Permissions/Read',
  request: {
    type: 'https://schema.identity.foundation/Hub/PermissionSetResourceBundle',
    path: '/style/v1.0/en-us'
  },
  payload: [
    {
      data: {
        "@type": 'https://schema.identity.foundation/Hub/PermissionSetResourceBundle',
        "language": "en-us",
        "consent_string_short": "View your clothing preferences",
        "consent_string_long": "Read your sizes...including...and your...favorite...",
        “icon”: “/resources/clothing.ico” // a recommended icon to display to users
      }
    }
  ]
}

OPEN: Should we instead use the OAuth Dynamic Client Registration scheme for internationalization? (section 2.2 of this doc)? Are there fallback languages if a language is not found? Does “en” map to “en-us”? There’s a fair bit of detail in internationalization which is not covered here. Should we just call it out of scope for this doc?

  1. If no acceptable strings are found, fail the request with a standard error message (see below).
  2. The user agent displays a consent prompt to the user (see below).
  3. The user can review permissions and accept the request. If the user denies, fail the request with an error message.
  4. The user agent constructs the PermissionGrant(s) and writes them to the user’s Hub:
{
  iss: 'did:example:12345', // the user’s DID
  aud: 'did:example:12345', // the user’s DID
  '@type': 'Permissions/Create',
  request: {
    type: ‘https://schema.identity.foundation/Hub/PermissionGrant’
  },
  payload: [
    {
      data: {
        "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
        "grantee": "did:example:67890", // the grantee’s DID
        "object_type": "https://schemas.clothing.org/measurements",
        "allow”: "-R--", // allows create, read, update, and delete
      }
    }
  ]
}
{
  iss: 'did:example:123abc',
  aud: 'did:example:12345',
  '@type': 'Permissions/Create',
  request: {
    type: ‘https://schema.identity.foundation/Hub/PermissionGrant’
  },
  payload: [
    {
      data: {
        "@type": ‘https://schema.identity.foundation/Hub/PermissionGrant’,
        "grantee": "did:example:67890",
        "object_type": "https://schemas.clothing.org/brandPreferences",
        "allow”: "-R--",
      }
    }
  ]
}
  1. The user agent returns a response to the indicated callback, via one of the transports described in the DID authentication documents. Where appropriate, the user may be redirected to the callback location as well. Some examples of transports include:
  2. Sending an HTTP POST request back to the callback URL specified in the request.
  3. Opening a URL with an OS specific custom scheme specified in the request.

The contents of the response include a JWT with the following structure:

{jwt-header}.
{
  “iss”: “did:example:12345”, // from DID auth spec
  “aud”: “did:example:67890”, // from DID auth spec
  “nonce”: ..., // from DID auth spec
  ...
  “granted”: [ // list of permission sets granted in this request
    “Hub://did:example:abc123/permissions/sets/style/v1.0”
  ]
}
.{jwt-signature}

In the event that an error occurs during the processing of the permission request, the response JWT may contain an error claim with one or more errors:

{jwt-header}.
{
  “iss”: “did:example:12345”, // from DID auth spec
  “aud”: “did:example:67890”, // from DID auth spec
  “nonce”: ..., // from DID auth spec
  ...
  “permission_errors”: [
    {
      “error”: “access_denied”,
      “error_code”: “ua-12345”
    }
  ],
}
.{jwt-signature}

Permission related errors are included in their own claim so that an attempt to authenticate a user with their DID request might still succeed, even though the requested permissions have been denied.

OPEN: if permissions are denied, should the entire authentication request fail? Is the ability to authenticate/sign-in/disclose a DID modeled as a permission? In other words, can a DID auth-flow complete without at least once “disclose” permission being granted to the requestor?

The user agent can choose from the following set of recommended errors (which come from OAuth), and may introduce new errors as necessary.

access_denied The user denied the request.
invalid_request The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.
invalid_permission The requested permission is invalid, unknown, or malformed.

The error code field is a non-standard string provided by the user agent that allows user agents to give developers greater detail about the reasons for an error that occurs. The accepted length of the error code field should be long enough to allow user agents to include a short URL to documentation about specific error codes.

2.3.2. SDK

The following is an API proposal for an example Android SDK which supports the requesting of permissions from a DID via their user agent. This is an example topology in which the custom scheme URI might be used to transport the permission request.

// set up SDK
String myDid = “did:example:12345”;
DIDClient client = DIDClient(myDid);
client.sign = new MyDidSigner(); // function to sign a request using app’s private key

// request permissions
String[] permissions = {"https://schemas.clothing.org/.permissions/style"};
Intent request = client.getPermissionRequestIntent(context, permissions);
client.getPermissionRequestResult(resultCode, request, new MyPermissionRequestHandler());

// implement callback handler
class MyPermissionRequestHandler implements DidCallback
{
  public void handleException(Exception exception)
  {
    // handle exception appropriately
  }

  public void handleResult(PermissionRequestResult result)
  {
    DIDDocument user = result.getDidDocument();
    DIDPermission[] granted = result.getGrantedPermissions();

    // check if necessary permissions were granted, then take action
  }
}

// implement signing function
class MyDidSigner implements DidSigner
{
  public string signRequest(string serializedPermissionRequest)
  {
    // sign request using app’s private key
  }
}

The following is an example of how the user agent’s consent experience could be rendered. Note all mocks in this document are for illustrative purposes only.

OPEN: What is the standard for specifying app logo/name? How is the identity for app verified? These are both important questions that have not been specified in this doc yet. Should they be brought into scope for this doc?

2.4. Security considerations

Because any DID can define a PermissionSet, user agents must take care to verify that the descriptions they display to users in consent dialogs accurately represent the access being granted. If user agents naively display the recommended strings defined in a PermissionSet, an attacker could easily define its own PermissionSet with dishonest descriptions.

For this reason, user agents must employ logic to determine if a DID (and therefore the PermissionSets it defines) is trustworthy. Some user agents may choose not to display any text but their own. If user agents choose not to trust a PermissionSet definition, they should not fail the request. Instead, they should take care to warn the user of potential attack, and make their best effort to ensure the user’s data remains safe.

Appendix

Alternative options considered

How to present user-friendly UI strings in user agent consent dialogs

Description Pros Cons
A When asking the user for permissions, client applications specify in requests the full schema of the objects they need to access. It is up to user agents to do their best to try and infer the proper UI strings from the structure of a permission request. User agents should never fail a request due to an unexpected/unknown object specified. - Very easy for new objects & permissions to be introduced to Hubs. Client apps can just request access to whatever schema they require, and it should work. - User agents can hard-code friendly strings for commonly used objects, but must fall back to presenting raw schema URIs to users. Raw schema URIs are unintuitive & difficult to localize. Users will not gain a good understanding of what access is being requested.
- Permission requests become very long & verbose – we’ve had problems in the past with request length limits on certain platforms.
B Each user agent independently defines a set of permissions that client developers can request and publishes its supported permissions online. User agents define the localized UI strings for these permissions. User agents might coordinate on a commonly used set of permissions, but there is no standard for what a user agent must support. Client developers can then choose which permissions to use based on which user agents they want to target. User agents fail a permissions request if an unsupported permission is requested. - All UI strings presented to users are defined by user agents. In this way, users only need to trust their UA to behave honestly. UAs who behave poorly will lose out to UAs who provide clear, honest representations of how data is accessed. - Forces developers to reason over UA support for permissions. Not all UAs may support the same set of permissions.
- Some UAs may disagree over the meaning of a permission. Developers might then get access to different objects depending on which UA the user users, and end up coding their app to be UA-specific.
- Onboarding a new permission/object type requires convincing UAs to add support for that permission. This could take significant time & discussion.
C The publisher of a schema (a schema provider) also publishes a set of permissions along with their schemas. Each permission therefore belongs to an organization, and is part of that organization’s namespace. These permissions include recommended localized UI strings that user agents can use, but user agents can also override with their own strings for commonly used permissions. If a user agent does not find strings published with a permission, it fails the request. - It’s up to specific industries & companies to organically arrive at the set of permissions and schemas most commonly used. Onboarding a new permission/object does not involve standards bodies or technology providers.
- User agents can present friendly UI strings without having to ship changes every time a new permission is added.
- Consensus on commonly used permissions may be difficult to achieve. Who gets to decide the world’s “profile” permission, for instance?
- A permission can’t span objects in different schemas. All objects that are included in a permission must be schematized by the same organization.
- Users place trust in schema providers to honestly describe the scope of access being granted.
D A discrete set of permissions that are supported in the DID ecosystem are agreed upon and defined by standards bodies. Standards bodies publish recommended localized strings for these permissions, but UAs can override them as they wish. If a permission request includes a non-standard permission, the UA must fail the request. - User agents have a discrete set of permissions to support at any point in time, making it easier to build high quality consent experiences.
- There is a central list of permissions that developers can browse when learning to code DID supporting apps.
- Permissions can be given short, friendly names that save space in requests.
- Permissions can span objects in multiple schemas.
- Onboarding a new permission/object type requires interfacing with a standards body. The standards body retains the power to define what permissions can/can’t be requested.
- It may be difficult for standards bodies to facilitate agreement between parties on the right permissions to adopt.
- Users place trust in standards bodies to honestly describe the scope of access being granted.
E When asking the user for permissions, client applications specify in requests the full schema of the objects they need to access, as well as the localized strings that should be presented in consent pages. - Users place trust in client apps to honestly describe the scope of access being requested.
- It’s extraordinarily easy for client apps to lie about what access they are requesting.

Research on existing authorization systems

Node accesscontrol

Is a pretty dead simple JSON based system that allows you to:

Example definitions of roles:

// grant list fetched from DB (to be converted to a valid grants object, internally) 
let grantList = [ 
  { role: 'admin', resource: 'video', action: 'create:any', attributes: '*, !views' }, 
  { role: 'admin', resource: 'video', action: 'read:any', attributes: '*' }, 
  { role: 'admin', resource: 'video', action: 'update:any', attributes: '*, !views' }, 
  { role: 'admin', resource: 'video', action: 'delete:any', attributes: '*' }, 
  { role: 'user', resource: 'video', action: 'create:own', attributes: '*, !rating, !views' }, 
  { role: 'user', resource: 'video', action: 'read:any', attributes: '*' }, 
  { role: 'user', resource: 'video', action: 'update:own', attributes: '*, !rating, !views' }, 
  { role: 'user', resource: 'video', action: 'delete:own', attributes: '*' } 
]; 
const ac = new AccessControl(grantList);

Reasons access-control does/doesn't make sense for us to use:

XACML

XACML is a policy languange based on XML for evaluating access requests. It consists of PolicySets, Policies, and Rules. Each of these refer to the following roles:

It then tacks on advanced authorization logic like Conditions, Obligations, and Targets.

A JSON version of a XACML access request might look like:

{
    "Request": {
        "AccessSubject": {
            "Attribute": [
                {
                    "AttributeId": "com.acme.user.employeeId",
                    "Value": "Alice"
                }
            ]
        },
        "Resource": {
            "Attribute": [
                {
                    "AttributeId": "com.acme.record.recordId",
                    "Value": "123"
                },
                {
                    "AttributeId": "com.acme.object.objectType",
                    "Value": "record"
                }
            ]
        },
        "Action": {
            "Attribute": [
                {
                    "AttributeId": "com.acme.action.actionId",
                    "Value": "view"
                }
            ]
        },
        "Environment": {
            "Attribute": []
        }
    }
}

Reasons XACML does/doesn't make sense for us to use:

User Managed Access (UMA)

UMA 2.0 Grant defines a new OAuth grant type, which does two main things:

UMA Federated Authorization decouples the authorization server from the resource server, so that one central authorization server can be used to secure many different types of resources, all using the same model. UMA-enabled systems can respect policies such as…

UMA implementations typically use XACML, but it's not technically required. It also integrates nicely with OpenID Connect, thought that's not enforced either.

Basic steps in the UMA flow:

  1. Client requests access to resource w/o an access token
  2. Resource server gets a "permission ticket" from the authorization server
  3. A permission ticket is a set of permissions, a permission is a set of scopes at a particular resource
  4. Which permissions are included in the permission ticket is up to the resource server to determine
  5. Resource server returns permission ticket to client in WWW-Authenticate header
  6. Client requests a "requesting party ticket" (RPT) (access token) from authorization server
  7. Client can hit token endpoint with permission ticket and optional "claim token", and optional "persisted claim token"
    1. A claim token is like an id_token or SAML assertion
    2. A persisted claim token is a token issued by the authorization server that represents previously validated claims
  8. Authorization server returns an error to client with a "need_info" error code
  9. Client redirects requesting party to "claims interaction endpoint", which performs claims gathering processes such as:
  10. Account registration/authentication to the authorization server as an identity provider
  11. Filling out a form
  12. Redirection or queries to another system to verify data/claims about the requesting party
  13. Authorization server redirects requesting party back to client with new permission ticket
  14. Client repeats step 4, asking for an RPT using its permission ticket, claim token, and persisted claim token
  15. Authorization server evaluates the request against policies configured by the resource owner
  16. Authorization server returns an RPT to the client, along with a PCT if appropriate
  17. Client sends RPT to resource server as a bearer token

Details on how you might implement this flow are provided in the UMA FedAuthZ spec: The UMA resource description is pretty much exactly what is described in this document:

{  
   "resource_scopes":[  
      "view",
      "http://photoz.example.com/dev/scopes/print"
   ],
   "description":"Collection of digital photographs",
   "icon_uri":"http://www.example.com/icons/flower.png",
   "name":"Photo Album",
   "type":"http://www.example.com/rsrcs/photoalbum"
}

But they also have a scope description which defines the resources for a scope in the above:

{
  "description":"Print out and produce PDF files of photos",
  "icon_uri":"[http://www.example.com/icons/printer](http://www.example.com/icons/printer)",
  "name":"Print"
}

Then, when a resource provider wants to issue a permissions ticket based on a query, it requests one from the authorization server using the resource ID (from the resource description creation request) and the scopes allowed for that resource:

HTTP POST
{
  "resource_id":"112210f47de98100",
  "resource_scopes":[
    "view",
    "[http://photoz.example.com/dev/actions/print](http://photoz.example.com/dev/actions/print)"
  ]
}

Reasons UMA does/doesn't make sense for us to use:

Azure RBAC

Core concepts:

Q: How does onboarding a new operation work? In the non-Azure case? A: There's a portal in which a company owner can come and add new operations for their providers

Q: How does onboarding a new role definition work? A: Customers can define custom roles via the portal, powershell, CLI, etc.

Q: So a grant records a scope, roledef, and princpal, right. Why not include the roledef in the grant itself? A: So that we can re-use roledefs over and over again without re-stating them, and change them in-place if necessary

Reasons Azure RBAC does/doesn't make sense for us to use:

Use cases for future consideration

The following use cases are not met by this proposal – they require advanced features that have not been included. Each use case, and the features required to meet it, should be considered in future additions to the Hub authorization layer.

Use case 3: Public search for DIDs

Alice uses a new DID based social media application. To use the app, she must to send friend requests to her contacts. Within the app, she searches for her friends using names as a search term, and might further qualify her searches using education or employment history.

New requirements for this case include:

Use case 4: Lightweight registration for a website

Alice wants to create an account with a news site, which allows her sign up using her DID. All that is required to access the news site is an email address. Upon signing up with her DID, the website requests access to Alice’s email address, which is part of Alice’s profile. However, the site does not receive access to the rest of Alice’s profile.

New requirements for this case include:

Use case 5: Sharing a verified claim

To apply for a job online, Alice needs to provide proof of her graduation from university. Thankfully her university allows her to link her DID to her student account and receive a digital diploma. After authenticating Alice’s DID, the university signs a digital diploma and writes the attestation to Alice’s Hub. Alice then applies for a job online with her DID, and the employer’s website is able to access her Hub and check for a signed proof of graduation.

New requirements for this case include:

Alice visits the Whole Foods website, which allows her to purchase groceries for delivery from the local store. Alice wishes to buy alcohol with her groceries, which requires age verification. The Whole Foods website allows Alice to prove her age via her DID – however, her age and delivery address must be confirmed by the state DMV to do so. Alice, wanting to minimize her data exposure, does not disclose her birthdate, but only the DMV verified fact that she is over 21. She also chooses to only allow Whole Foods access to her current shipping address, rather than all of the previous residences associated with her DID. |

New requirements for this case include:

Use case 6: Sharing collections of data

Alice is collaborating with fellow students at her university on a group project. The group uses a DID based document storage solution that is akin to Dropbox. All documents and files for their project are store in Alice’s Hub, and are organized into a specific folder she created. In order to collaborate, she grants her team access to only the files contained in this folder. She does not want her team to access all documents and files in her Hub, nor does she want to share out each file individually.

New requirements for this case include:

Use case 7: Sharing classes of verified information

Alice wants to sign up as a dog-sitter for a task-fulfilling service. She uses a website to enlist as a willing dog-sitter, but struggles to get any clients, since she has no previous reviews on the site. Instead, she can provide other assurance to potential clients by linking her DID to the website. The website is then able to access Alice’s verified proof of employment, and her verifiend proof of completion of a dog training course from a local school.

New requirements for this case include:

Use case 8: Improving relying party experiences

A developer of a site supporting DIDs wants to build a delightful experience for Alice. However, doing so is currently difficult for several reasons. Alice is scared by the many permissions the site requests and can’t easily recognize the site’s branding in her user agent. Making matters worse, integration with DIDs is so difficult that delivering DID support in the site has slipped for months.

New requirements to address these shortcomings include:

Use case 9: Impersonating the user in inter-Hub scenarios

Alice signs up for a new to-do list app that stores tasks in her Hub. The app allows Alice to assign tasks to friends & family and remind them when their tasks are due. The app sends automated reminders on Alice’s behalf, such that Alice’s family sees reminders are coming from Alice herself.

New requirements to enable such an app are:

Use case 10: Sharing time-based subsets of data

Alice has become part of a legal investigation, and wants to help provide evidence to authorities. To assist with the investigation, Alice wishes to share her call and message records from April-June of this year. However, she doesn’t wish to disclose any records beyond the time period that is relevant for the investigation.

New requirements to enable this case are:

Use case 11: Granting access to actions, rather than data

Alice purchases a home security system that includes digitally controlled locks for her doors. When setting up the system, Alice was able to associate each lock with her DID, such that all operations of the system can be done via her Hub. Alice is now leaving for vacation, and wants to give a friend access to her home for the weekend. She wants her friends access to expire automatically when she returns from vacation.

New requirements to enable this case are:

Callbacks

When developing against identity Hubs, developers will want their apps and services to stay aware of changes to user data over time. For instance, if a user updates their email address in their identity Hub’s profile, an application will want to update its local copy used for communication with the user.

With the current proposal, doing so would require apps to query all Hubs periodically to check for changes to data they are interested in. A better model would be to allow apps and services to register a callback or webhook that can be pinged when certain data in a user’s Hub changes. This will prevent unnecessary traffic to Hubs.

Our early thinking is that callbacks could be included as an extension of our authorization model. For instance, an application could register for a callback by requesting the following permission:

PermissionGrant

{
  "owner": "did:example:12345",
  "did": "did:example:67890",
  "object_type": “https://schemas.clothing.org/measurements”,
  "allow": "-R--",
  "callbacks": [
    {
      "events": "C-UD",
      "uri": {callback-uri},
      "headers": {desired-header-values}
    }
  ]
}

A PermissionGrant with this callback configuration would send callback messages any time an object of type “https://schemas.clothing.org/measurements” is created, updated, or deleted.

We’ve also considered modeling callbacks separately from permissions. However, there is an important connection between permissions and callbacks: the rules from the authorization model should apply to callbacks just as they apply to regular Hub queries. More concretely, a third party should not be able to register or receive a callback if they do not have at least the “read” permission for the given object type.

  1. https://en.wikipedia.org/wiki/XACML
  2. https://oauth.net/2/
  3. See appendix for recommendations on future work
  4. See appendix for early thoughts on callbacks
  5. TODO: link
  6. DID Auth documents
  7. DID Auth documents