...
Background
Currently content update is done by the user who created it which creates problem and is complex in scenarios where an admin want to update content by retired user. There should be an easy way to allow users to update content at the same time it should have authentication.
Solution Approach
approach 1 :
There should be a way to allow certain operations and for that we need to maintain master keys. These master keys would be created and stored through API by admin and would be used by anyone to bypass the current authentication mechanism which restricts some flow.
There should be APIs to create, fetch and verify the master key.
create API
...
nopanel | true |
---|
...
Problem Statement 1
What is the approach to generate the key?
Solution 1
Create JWT token with data passed upon. A JWT token will be created using secret key and below data
- channel
- name
- description
- organisationId
- createdBy
- createdOn
Solution 2
Custom generated time based key.
Solution 3
Using keycloak to generate an verify token to be used as masterkey.
Pros and Cons
Key Generation Approach | Pros | Cons |
---|---|---|
JWT | no need to store the key in DB | |
Custom | method is available so negligible time consuming | key needs to be stored in db |
Keycloak | comparatively faster to implement | no support for channel or organisation based key key would expire based on keycloak configuration |
Problem Statement 2
What are the APIs required to manage master keys?
API Specifications
Create API
name and channel / organisationId is mandatory.
URL:
POST /v1/auth/masterkey/create
Headers:
- Authorization
- x-authenticated-user-token
Request Params:
param name | type | description |
---|---|---|
channel | String | channel for which master key is applicable for, if not provided can be calculated from organisationId |
name | String | consumer name |
organisationId | String | organisationId for which master key is applicable for, if not provided can be calculated from channel |
Response Params:
param name | type | description |
---|---|---|
key | String | generated master key, mapped with the requested channel/organisationId and name |
Errors:
status code | error code | error message |
---|---|---|
400 | INVALID_ORG_DATA | Given Organization Data doesn't exist in our records. Please provide a valid one. |
400 | MANDATORY_PARAMETER_MISSING | Mandatory parameter {channel/organisationId, name} is missing. |
400 | PARAMETER_MISMATCH | Mismatch of given parameters: channel, organisationId. |
400 | KEY_EXISTS | Key exists for given channel {} and consumer {} |
Sample Request and Response:
No Format | ||
---|---|---|
| ||
Request { request : { "channel" : "sunbird", "name" : "DikshaImplTeam", "orgId" : "01262366359399628812" } } Response { "id": "api.auth.masterkey.create", "verresult": "v1", "ts": "2018-01-29 11:12:31:853+0000", "params": { "resmsgid": null, "msgid": "8e27cbf5-e299-43b0-bca7-8347f7e5abcf", "err": "KEY_EXISTS", "status": "KEY_EXISTS", "errmsg": "Key exists for given channel sunbird" }, "responseCode": "CLIENT_ERROR", "result": { } } |
The key would be stored in DB with the argument passed
Table Structure
...
{
"key" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVsIjoic3VuYmlyZCIsIm5hbWUiOiJEaWtzaGFJ bXBsVGVhbSIsImNyZWF0ZWRCeSI6MTUxNjIzOTAyMiwiY3JlYXRlZE9uIjoxNTE2MjM5MDIyLCJleHBpcmVzT24iOjE1MTYyNDkwMjIsIm9yZ0lkIjoiMjM0NTY1NDU2In0.Cs5-FW7OHip6njkQvMP6zpIVB5Q-xLLgz_jnYW3zPOw"
}
} |
Verify API
URL:
POST /v1/auth/masterkey/verify
Headers:
- Authorization
Request Params:
param name | type | description |
---|---|---|
key | String | master key to verify |
Response Params:
param name | type | description |
---|---|---|
channel | text | primary key consist channel name |
key | text | master key generated |
createdby | text | user who created the master key |
createddate | timestamp | created time |
In addition a TTL will be put on the entry for a set time configured in properties file
get API
No Format | ||
---|---|---|
| ||
GET /v1/masterkey/{channel}
Response body : (Success) 200
{
"id": "api.masterkey",
"ver": "v1",
"ts": "2019-01-29 09:17:31:909+0000",
"params": {
"resmsgid": null,
"msgid": "9db786d3-45c2-447d-b657-f9768da15652",
"err": null,
"status": "success",
"errmsg": null
},
"responseCode": "OK",
"result": {
"key" : "1fb786d3-45c2-447d-b657-f9768da15348",
"expiresOn": 604800
}
}
Response body : (Error) 404
{
"id": "api.masterkey",
"ver": "v1",
"ts": "2018-01-29 11:12:31:853+0000",
"params": {
"resmsgid": null,
"msgid": "8e27cbf5-e299-43b0-bca7-8347f7e5abcf",
"err": "KEY_NOT_EXISTS",
"status": "KEY_NOT_EXISTS",
"errmsg": "Key does not exists for given channel sunbird"
},
"responseCode": "RESOURCE_NOT_FOUND",
"result": {
}
} |
verify API
...
nopanel | true |
---|
...
String | channel name related to master key | |
name | String | consumer name |
description | String | describes the purpose for master key |
organisationId | String | organisation Id mapped with key |
createdBy | String | userId of the person who created the key |
createdOn | timestamp | timestamp when master key was created |
Errors:
status code | error code | error message |
---|---|---|
400 | INVALID_KEY | Given master key is invalid |
400 | MANDATORY_PARAMETER_MISSING | Mandatory parameter key is missing. |
Sample Request and Response:
No Format | ||
---|---|---|
| ||
Request { request : { "channelkey" : "sunbird", "key" : "1fb786d3-45c2-447d-b657-f9768da15348eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVsIjoic3VuYmlyZCIsIm5hbWUiOiJEaWtzaGFJ bXBsVGVhbSIsImNyZWF0ZWRCeSI6MTUxNjIzOTAyMiwiY3JlYXRlZE9uIjoxNTE2MjM5MDIyLCJleHBpcmVzT24iOjE1MTYyNDkwMjIsIm9yZ0lkIjoiMjM0NTY1NDU2In0.Cs5-FW7OHip6njkQvMP6zpIVB5Q-xLLgz_jnYW3zPOw" } } Response body : (Success) 200 { "id": "api.auth.masterkey.verify", "verresult": "v1", "ts": "2019-01-29 09:17:31:909+0000", "params": { "resmsgidchannel": null, "msgid": "9db786d3-45c2-447d-b657-f9768da15652"sunbird", "err": null, "status "name": "successDikshaImplTeam", "errmsg": null }, "responseCode "description": "OK", "result": { } } Response body : (Error) 400 { "idorganisation content" "organisationId": "api.masterkey.create01262366359399628812", "vercreatedBy": "v1", "ts": "2018-01-29 11:12:31:853+0000", "params": { "resmsgid": null, "msgid": "8e27cbf5-e299-43b0-bca7-8347f7e5abcf", "err": "INVALID_KEY", "status": "KEY_NOT_EXISTS", "errmsg": "Provided key for channel sunbird is invalid" }, "responseCode": "CLIENT_ERROR", "result": { } } |
approach 2:
Previous approach is configured to create a master key only based on channel. This can be modified to create a master key based on organisation too. The changes we will have is we can pass type in the request too. The generated key will be stored with type and value as (channel, abc) or (orgId, "org01")
No Format | ||
---|---|---|
| ||
Request body :
{
request : {
"value" : "sunbird",
"type" : "channel" //channel or orgId
}
} |
get API will be modified to include the type
GET /v1/masterkey/{type}/{value}
The verify API will include additional type parameter
No Format | ||
---|---|---|
| ||
{
request : {
"value" : "sunbird",
"type" : "channel",
"key" : "1fb786d3-45c2-447d-b657-f9768da15348"
}
} |
This also means that table will have another column "type" and it will be used to fetch key accordingly.
Other behavior remains same
approach 3:
In previous two approaches we are considering a master key which will be expired after certain duration. But it can be modified to include a refresh token which can be used to generate a new master key. Note that refresh token has it's own expiry, post that it requires to create a new master key and refresh token by create API call.
The benefit of this is that it helps in mitigate leaking of master key by making the expiry duration of short intervals.
No Format | ||
---|---|---|
| ||
POST /v1/masterkey/create Request body : 00dd6646-be73-4fb0-b676-ccd01bda085e", "createdOn":1548932815386, } } |
Delete API
URL:
POST /v1/auth/masterkey/delete
Headers:
- Authorization
- x-authenticated-user-token
Request Params:
param name | type | description |
---|---|---|
channel | String | channel for which master key needs to be deleted |
name | String | consumer name |
organisationId | String | organisationId for which master key needs to be deleted |
Response Params:
None
Errors:
status code | error code | error message |
---|---|---|
400 | MANDATORY_PARAMETER_MISSING | Mandatory parameter {channel/organisationId, name} is missing. |
400 | KEY_NOT_EXISTS | Key does not exists for given channel {} and consumer {} |
Sample Request and Response:
No Format | ||
---|---|---|
| ||
Request { request : { "channel" : "sunbird" } } Response body : (Success) 200 { "id": "api.masterkey.create", "ver": "v1", "ts": "2019-01-29 09:17:31:909+0000", "params": { "resmsgid": null, "msgid": "9db786d3-45c2-447d-b657-f9768da15652", "err": null, "status": "success", "errmsg": null }, "responseCode": "OK", "result": { "keyname" : "1fb786d3-45c2-447d-b657-f9768da15348DikshaImplTeam", "expiresOn": 120, "refreshToken": "3ab586d3-45c2-447d-b657-g9768da13730" } } request to regenerate the key need refresh token { request : { "channel" : "sunbird", "refreshToken": "3ab586d3-45c2-447d-b657-g9768da13730" orgId" : "01262366359399628812" } } Response body (Success : 200) { "id": "api.auth.masterkey.create", "ver": "v1", "ts": "2019-01-29 11:17:31:909+0000", "params": { "resmsgid": null, "msgid": "9db786d3-45c2-447d-b657-f9769da15652", "err": null, "status": "success", "errmsg": null }, "delete", "responseCode": "OK", "result": { "key" : "3gh686e3-45c2-447d-b657-b3364da84351", "expiresOn": 120, "refreshToken": "3ab586d3-45c2-447d-b657-g9768da13730" } } |
There would be a TTL on the refresh token, so as the entry gets removed after refresh token expiry and then it would require to create a fresh entry.
get API response
No Format | ||
---|---|---|
| ||
{
"id": "api.masterkey.create",
"ver": "v1",
"ts": "2019-01-29 11:18:31:909+0000",
"params": {
"resmsgid": null,
"msgid": "9db786d3-45c2-447d-b657-f9769da15652",
"err": null,
"status": "success",
"errmsg": null
},
"responseCode": "OK",
"result": {
"key" : "3gh686e3-45c2-447d-b657-b3364da84351",
"expiresOn": 30,
"refreshToken": "3ab586d3-45c2-447d-b657-g9768da13730"
}
}
Response for expired key : (Error) 400
{
"id": "api.masterkey.create",
"ver": "v1",
"ts": "2018-01-29 11:21:31:853+0000",
"params": {
"resmsgid": null,
"msgid": "8e27cbf5-e299-43b0-bca7-8347f7e5abcf",
"err": "INVALID_KEY",
"status": "INVALID_KEY",
"errmsg": "Either he key doesn't exists or it has been expired"
},
"responseCode": "CLIENT_ERROR",
"result": {
}
}
|
Problem Statement 3
What is the DB design for storing information about master keys?
approach 1:
JWT token Table Structure
column | type | description |
---|---|---|
channel* | text | consist user provide channel name |
name* | text | provided by user |
organisationId | text | provided by user or root org id mapped with channel |
createdBy | text | user id who created the master key |
createdOn | timestamp | created time |
approach 2:
custom generated key
column | type | description |
---|---|---|
channel* | text | consist user provide channel name |
name* | text | provided by user |
organisationId | text | provided by user or root org id mapped with channel |
key | text | holds custom generated key |
createdBy | text | user id who created the master key |
createdOn | timestamp | created time |
Problem Statement 4
How to use generated master key to bypass existing user authentication?
approach 1:
- master key will be passed in X-authenticated-user-token
- use an extra header (X-authentication-type) declaring the type of authentication as master( for master key and user for user authentication).
approach 2:
- extra header (X-authentication-master-key) to pass master key.
- If above header is present it supersedes the user authentication
Pros and Cons
Key Usage Approach | Pros | Cons |
---|---|---|
X-authentication-type | In future it can support additional authentication types |