Problem Statement
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, get and verify the master key.
create API
it will be used to create a key with parameters channel, name and orgId.
Approach to generate Key
- Create JWT token with data passed upon. A JWT token will be created using secret key and below data
- channel
- createdBy
- createdOn
- name
- orgId
- expiresOn
- Custom generated time based key
key generation type | 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 |
POST /v1/masterkey/create Request body : { request : { "channel" : "sunbird", // channel name for which master key is generated "consumer" : "DikshaImplTeam", // consumer name who will use the key "orgId" : "01262366359399628812" // optional orgId to make the key org specific, default value is rootOrgId of channel } } 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": { "key" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVsIjoic3VuYmlyZCIsIm5hbWUiOiJEaWtzaGFJ bXBsVGVhbSIsImNyZWF0ZWRCeSI6MTUxNjIzOTAyMiwiY3JlYXRlZE9uIjoxNTE2MjM5MDIyLCJleHBpcmVzT24iOjE1MTYyNDkwMjIsIm9yZ0lkIjoiMjM0NTY1NDU2In0.Cs5-FW7OHip6njkQvMP6zpIVB5Q-xLLgz_jnYW3zPOw" } } Response body : (Error) 400 { "id": "api.masterkey.create", "ver": "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 and consumer DikshaImplTeam" }, "responseCode": "CLIENT_ERROR", "result": { } }
Table Structure
column | type | description |
---|---|---|
channel* | text | consist user provide channel name |
consumer* | text | provided by user |
orgId | text | provided by user or root org id mapped with channel |
createdby | text | user id who created the master key |
createdon | timestamp | created time |
expireson | timestamp | when the token will be expired |
lastUpdatedBy | text | user who updated the token |
lastupdatedOn | timestamp | time when token was updated |
Errors
status code | error code | error message |
---|---|---|
400 | INVALID_CHANNEL | Channel value is Invalid |
400 | MANDATORY_PARAMETER_MISSING | Mandatory parameter {channel, name} is missing. |
400 | PARAMETER_MISMATCH | Mismatch of given parameters: channel, orgId. |
get API
POST /v1/masterkey/get Request body : { request : { "channel" : "sunbird", "consumer" : "DikshaImplTeam" } } 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" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVsIjoic3VuYmlyZCIsIm5hbWUiOiJEaWtzaGFJ bXBsVGVhbSIsImNyZWF0ZWRCeSI6MTUxNjIzOTAyMiwiY3JlYXRlZE9uIjoxNTE2MjM5MDIyLCJleHBpcmVzT24iOjE1MTYyNDkwMjIsIm9yZ0lkIjoiMjM0NTY1NDU2In0.Cs5-FW7OHip6njkQvMP6zpIVB5Q-xLLgz_jnYW3zPOw" } } 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
POST /v1/masterkey/verify Request body : { request : { "key" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGFubmVsIjoic3VuYmlyZCIsIm5hbWUiOiJEaWtzaGFJ bXBsVGVhbSIsImNyZWF0ZWRCeSI6MTUxNjIzOTAyMiwiY3JlYXRlZE9uIjoxNTE2MjM5MDIyLCJleHBpcmVzT24iOjE1MTYyNDkwMjIsIm9yZ0lkIjoiMjM0NTY1NDU2In0.Cs5-FW7OHip6njkQvMP6zpIVB5Q-xLLgz_jnYW3zPOw" } } Response body : (Success) 200 { "id": "api.masterkey.verify", "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": { "channel": "sunbird", "consumer": "DikshaImplTeam", "createdBy": "00dd6646-be73-4fb0-b676-ccd01bda085e", "createdOn":1516239022, "expiresOn":1516249025, "orgId": "01262366359399628812" } } Response body : (Error) 400 { "id": "api.masterkey.create", "ver": "v1", "ts": "2018-01-29 11:12:31:853+0000", "params": { "resmsgid": null, "msgid": "8e27cbf5-e299-43b0-bca7-8347f7e5abcf", "err": "INVALID_KEY", "status": "INVALID_KEY", "errmsg": "Provided key for channel sunbird is invalid" }, "responseCode": "CLIENT_ERROR", "result": { } }
approach 2:
In previous approach 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.
POST /v1/masterkey/create Request body : { 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": { "key" : "1fb786d3-45c2-447d-b657-f9768da15348", "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" } } Response body (Success : 200) { "id": "api.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 }, "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
{ "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 the key doesn't exists or it has been expired" }, "responseCode": "CLIENT_ERROR", "result": { } }
Table structure
column | type | description |
---|---|---|
channel | text | primary key as channel name |
key | text | master key generated |
refresh_token | text | refresh token generated |
key_expiry | timestamp | time when current master key will be expired |
createdby | text | user id of the user who created the entry |
createdon | timestamp | time when entry was created |