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 untilexpiration
, eternal ifexpiration
isnull
- The
permission_request
will be revoked automatically afterlimit
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:
- Some permissions are granted;
- Request is denied;
- 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.