RFC: Wallet Authorization Spec Proposal

Abstract

This specification describes the authorization and authentication process between security-sensitive wallets and untrusted applications.

Since key-related resources should not be accessible to any untrusted applications, such as a website, until the user grants specific permissions on it, most of the endpoints should be guarded by an authentication system.

This specification defines some aspects of the authentication system, including:

  • Depiction of permission;
  • Two JSON-RPC methods to get and request permissions;
  • Two expressive user interfaces to inform permissions to grant and revoke;
  • Authorization process between application, wallet, and user;
  • Authentication process between application and wallet;

Introduction

Wallets co-operating with untrusted applications are doing works viz.

  • Provide available addresses;
  • Provide live cells (including deps) for constructing transactions;
  • Switch between blockchain nodes to submit transactions;
  • Sign and send transactions proposed from applications;
  • Sign and verify messages from applications.

Some of them should be prohibited until the owner of the wallet grants related permissions on the requester.

For universal experience between a variety of applications and wallets, this specification proposes a canonical process for these tasks. Following this guide, developers can implement permission systems in a general way, and users can avoid the mental burden on permission-grants between different applications.

Specification

Permission Restriction

This specification imposes three restrictions on each permission,

restrictions:
  deps: string[]              # required permissions for this permission
  expiration: string | null   # timestamp of expiration datetime, null for permanent
  limit: string | null        # limitation of invocation, null for unlimited

Permission Requests

This specification defines two requests in JSON-RPC method:

Get Permission List

get_permission_list queries the permission list provided by the wallet. On handling this kind of request, the wallet should return the list of permissions in the following format.

permission_name:
  is_granted: boolean # is granted or not
  restriction:
    deps: string[]            # required permissions for this permission
    expiration: string | null # timestamp of expiration datetime, null for permanent
    limit: string | null      # limitation of invocation, null for unlimited

Request Permissions

To request particular permissions, application should POST a request in following format:

id: 1
jsonrpc: '2.0',
method: 'request_permissions',
params:
  app:
    name: string
    description: string | null
  permissions:
    permission_name:
      restriction:
        expiration: string | null
        limit: string | null
      reason: string | null

The payload in this request indicates the details as follows:

  • Application is requesting permission_name permission
  • The permission_request should be available until expiration, eternal if expiration is null
  • The permission_request will be revoked automatically after limit invocations, null for unlimited.
  • Reason to request permission_name

Users should be informed with all this information in a proper way, including extra info about the requester.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ **         Request for Permissions         ** β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Application : app_name                        β”‚
β”‚ Description : app_description                 β”‚
β”‚ Origin      : app_origin                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ **          Requested Permissions          ** β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [x] name β”‚ expiration time β”‚ invocation limit β”‚
β”‚          β”œ-----------------β”΄------------------─
β”‚          β”‚ Reason                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [x] name β”‚ expiration time β”‚ invocation limit β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚          Deny          β”‚         Grant        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

With this interface, requested permissions can be granted as needed, which makes the permission system more flexible.

Once user confirms, wallet should return:

id: 1
jsonrpc: '2.0'
result:
  permissions:
    permission_name:
      is_granted: boolean
      message: string | null   # reason for rejection, user rejected, permission unrecognized, dep permissions are not granted, etc.
  error:     # unexpected error thrown in the procedure, like database error, etc
  message:   # expected exception, such as user denied, password incorrect, permission deps required

If user denies, the wallet should return:

id: 1
jsonrpc: '2.0'
result:
  code: 401
  message: permission request is denied

Revoke Permissions

A grant should be able to revoke freely in a kind way with essential annotations:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ **            Revoke Permission            ** β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Application : app_name                        β”‚
β”‚ Origin      : app_origin                      β”‚
β”‚ Permission  : permission_name                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Dependents  β”‚ permission_name                 β”‚
β”‚             β”œ---------------------------------─
β”‚             β”‚ permission_name                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         Cancel        β”‚        Confirm        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authorization Process

The authorization process is a sequence of simple tasks that including application requests permissions, inform user necessary information, handle confirmation/rejection/exception.

This specification has defined a method to request permissions and an advisable user interface to ask the user for confirmation. Now the handlers on confirmation, rejection, and exception should be complemented in this procedure.

There are three kinds of result in the end:

  1. Some permissions are granted;
  2. Request is denied;
  3. Exception occurs.

All these cases are covered by three fields of the result of request_permissions defined above:

permissions:   # result of request_permissions mentioned above, null for an exception
error:         # unexpected error thrown in the procedure, like database error, etc
message:       # expected exception, such as user denied, password incorrect, permission deps required

error and message are worthy distinguishing since they describe different kinds of exception.

error means an unexpected exception is thrown, like database error or internal exception. While message indicates an expected behavior is delivered, e.g. user denied, or password is incorrect, etc.

With all of these definition, the workflow should be much clearer:

Authentication Process[Discretionary]

Defining the process of authentication is good for writing SOLID code but it’s not mandatory in this system.

Name authentication mechanism Strategy, which is a concept from passport, and classify them into LocalStrategy, JwtStrategy, etc. by concrete mechanisms (More strategies can be found in Passport).

Suppose an endpoint get_xxx should be protected by the auth module auth.

interface Verify {
  (payload: string) : {
    permissions: Permission[] | null,
    error?: Error | null,
    message?: string | null
  }
}

class Strategy<T extends Verify> {

  #name: string
  #verify: T

  get name() {
    return this.#name
  }

  public constructor(name: string, verify: T) {
    this.#verify = verify
    this.#name = name
  }

  public validate(req: HttpRequest): ReturnType<Verify> {
    const { payload } = this.getPayload(req)
    return this.#verify(payload)
  }

  protected getPayload: (req: HttpRequest) => { payload: string }
}

// Register authentication strategy
auth.use(new Strategy(
  'custom',
  (payload: string) => {
    const permissions = app.getPermissions(payload)
    if (!permissions.xxx.is_granted) {
      return {
        permissions: null,
        message: `Permission xxx is not granted`
      }
    }
    return {
      permissions,
    }
  }
))
// Protect endpoint with speicified strategy
app.post(`get_xxx`, auth.authenticate('custom'))

With this configuration, app invokes auth.validate on each post request before get_xxx route handler and if validation fails, the message will be passed to the router handler for the next step.

Conclusion

This specification illustrates several facets of authorization and authentication from perspectives of applications, wallets, and users. With the APIs defined in this specification, developer experience is unified across different applications while with the request/revoke UIs pictured above, the user experience is also improved across various wallets.

References

3 Likes

Any suggestions would be appreciated :beers:

How is dependency permissions handled in β€œRequest Permissions”? Does the app must request dependencies before it request the permission? How are dependencies displayed to users?

How is dependency permissions handled in β€œRequest Permissions”? Does the app must request dependencies before it request the permission?

It’s something of implementation. For me, the best solution is to handle dependants and dependencies at the same time.

A wallet can pick a positive way to inform the user, which is my cup of tea. Say, Permission A, which relies on Permission B, is requested, then both of them should be listed in UI and a message can be appended to Permission B by the wallet.

Or a negative way to reject the DApp if a permission’s dependency is not requested together and not granted before.

How are dependencies displayed to users?

If the positive way above is adopted, a user can distinguish dependencies by auto-appended messages in the following method:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ **          Request for Permissions          ** β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Application : app_name                          β”‚
β”‚ Description : app_description                   β”‚
β”‚ Origin      : app_origin                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ **           Requested Permissions           ** β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [x] Perm_A β”‚ expiration time β”‚ invocation limit β”‚
β”‚            β”œ-----------------β”΄------------------─
β”‚            β”‚ Requested for blah blah            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [x] Perm_B β”‚ expiration time β”‚ invocation limit β”‚
β”‚            β”œ-----------------β”΄------------------─
β”‚            β”‚ Requested due to Perm_A            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ [x] Perm_C β”‚ expiration time β”‚ invocation limit β”‚
β”‚            β”œ-----------------β”΄------------------─
β”‚            β”‚ Requested for blah blah            β”‚
β”‚            β”‚ # Perm_C is a dep of Perm_A        β”‚
β”‚            β”‚ # but it's also requested by DApp  β”‚
β”‚            β”‚ # so message from DApp is prior    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚           Deny         β”‚           Grant        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When the negative one is used, the user has no worries about the relations between permissions and thus the display of them is redundant.