CAM

Central Account Management (CAM) is a Whole-of-Government (WOG) system that uses a standardised application interface to manage user accounts and access rights for different agency applications. The interface standard is based on System for Cross-domain Identity Management (SCIM).

Any application that we build for a government agency will likely have to implement REST APIs according to CAM specifications so that the CAM Agent can manage user accounts and access rights using the APIs.

If your application has to implement CAM APIs, the agency should have provided the CAM API Interface Specs for reference.

Demo Project

A sample project that implements CAM authorisation and endpoints can be found here .

Before looking through the project, it is recommended to go through this document to gain a better understanding of CAM.

Authorisation Attribute

CAM requires us to implement authorisation checks for all CAM REST API endpoints. We will make use of a custom authorisation attribute to apply these checks to all the relevant endpoints.

To learn more about how to implement custom authorisation attributes in ASP.NET Core, read this

Typically, we will need to check for the following:

  • Nonce
  • Timestamp
  • Account ID
  • Authorisation Header Signature, signed using a secret key

Refer to the CAM demo project to explore the code for the custom authorisation attribute.

CAM Settings

There are a number of settings that will need to be configured and changed depending on the environment (DEV, UAT and PROD). This will be done using the .NET IOptions pattern.

To learn more about how to use the IOptions pattern for configuration, read this

Sample CAM configuration settings

public class CamConfigurationSetting
{
    public string AccountId { get; set; } = string.Empty;
    public string SecretKey { get; set; } = string.Empty;
    public string GracePeriod { get; set; } = string.Empty;
    public string CamDomainName { get; set; } = string.Empty;
}

Which will correspond to the following in appSettings.json

{
  "CamConfigurationSetting": {
    "AccountId": "accid",
    "SecretKey": "secret",
    "GracePeriod": "1000",
    "CamDomainName": "https://localhost:7002"
  }
}

Explanation for CamDomainName

The authorisation header signature usually comes from a hash of a combined string, which contains:

  • HTTP Method
  • URL
  • Sorted name value pair parameters

Example combined string: POST&https://intranet.app1.agency1.gov.sg/scim/api/users?accountid=qw1er2ty3ui4&nonce=09832472134&ts=5678973453

The URL will be based on the domain name that users will use to access our backend APIs. In many cases, UAT and PROD environments will have our backend APIs hosted behind some reverse proxy. As such, we will not be able to retrieve the correct domain name when using context.HttpContext.Request.Host.Value.ToLower() . Domain name retrieved from HttpContext could be something like localhost:7002 whereas actual domain name used could be intranet.app1.agency1.gov.sg.

In order to overcome this, we define the correct domain name in the configuration settings and use that.

CAM API Endpoints

Here is a non-exhaustive list of APIs that will need to be implemented:

  • Get User
  • Get User List
  • Disable/Enable User
  • Remove User
  • Get Group
  • Get Group List
  • Add/Remove User from Group
  • Remove Group

The CAM API Interface Specs may mark certain APIs as for future use. Such API should be implemented at a lower priority and kept disabled by returning HTTP 500.

For removal of user/group, DO NOT delete the entry from database. Instead use a boolean flag to mark it as deleted

Refer to CAM API Interface Specs provided by agency for what each endpoint needs to do.

CAM Schemas in Response Body

In the sample response body for each API, we will see schemas such as:

"schemas": [
 "urn:ietf:params:scim:schemas:core:2.0:User",
 "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
 "urn:ietf:params:scim:schemas:extension:cam:2.0:User"
],
/* omitted for brevity */
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
 "organization": "<Agency Code>",
 "division": "<Division>",
 "department": "<Department>",
 "manager": {
   "value": "<RO User ID>",
   "$ref": "<RO User Profile URI>",
   "displayName": "<RO Name>"
  }
},
"urn:ietf:params:scim:schemas:extension:cam:2.0:User": {
 "lastLogin": "<Last Login Date Time>",
 "lastPasswordChanged": "<Last Password Changed Date Time>",
 "isPrivileged": <Is Privilege User>
}

To include these schemas in the response body, we first define the schema strings as constants in a file that stores all CAM related constants. As these schemas are unlikely to change, we can define them as const string in the code.

For each response, we create a response class and set the schemas:

public class GetUserResponse
{
    public IEnumerable<string> Schemas { get; set; } = Constants.User.Get.Schemas;
    
    /* omitted for brevity */
    
    [JsonPropertyName(Constants.User.Get.EnterpriseSchema)]
    public EnterpriseUserResponse EnterpriseUser { get; set; } = new();

    [JsonPropertyName(Constants.User.Get.ExtensionSchema)]
    public ExtensionUserResponse ExtensionUser { get; set; } = new();
}

Testing

For testing of CAM implementation, agency should provide a Postman script that we can import into Postman.

After importing the script:

  1. Navigate to the Pre-request Script tab in Postman and set host, port, accountId and secretkey as defined in your CamConfigurationSetting
  2. Select an API request to test
  3. Change the request body in Postman before sending a request
  4. Examine the response and console logs for details