Cosmos DB Security & REST API
To connect to SQL Server, usually you need to have a username and password. If you have the right username and password combination, you should be set to run your queries. You need to do more than that to connect and run queries in CosmosDB. For example, you can call CosmosDB from web browsers and retrieve data without hitting to any web server. To be able to do that, you are going to need much more than username and password.
Let’s look at the Keys page of Cosmos DB. There are two types of keys, one set can Read
and Write. Other set can only Read. It
is a good practice to have different connection strings in your applications. Read queries should use Read connection string, Write queries should use the Write connection string. This applies to SQL Server too.
Try to use the Secondary Key as much as you can in your applications. Sometimes basic connection string can give all kind of troubles to me in SQL Server. This setup of CosmosDB can be tricky. It’s very important for developers to understand how the authentication works. Before I continue, I want to be sure that you understand the following CosmosDB options.
Try to use the Secondary Key as much as you can in your applications. Sometimes basic connection string can give all kind of troubles to me in SQL Server. This setup of CosmosDB can be tricky. It’s very important for developers to understand how the authentication works. Before I continue, I want to be sure that you understand the following CosmosDB options.
Master Keys
These keys are created during the creation of your Cosmos DB
Account. You can click on refresh icon to regenerate them at Cosmos DB Portal.
You cannot regenerate master keys in Emulator. These keys provide access to the
administrative resources and you should be very careful when you need to store
them. Use Read-Only Keys as much as you can.
Resource Tokens
Resource Tokens are responsible for providing access to
specific containers, documents, attachments, stored procedures, triggers, and
UDFs. Each user must have a resource token. Your application needs to use a
resource token to call Cosmos DB.
Users
Users are specific for Cosmos DB databases. You can attach
specific permissions or roles to each user.
REST API
You have many options to access to CosmosDB. Rest API is one
of these options and it is the low level access way to Cosmos DB. You can customize
all options of CosmosDB by using REST API. To customize the calls, and pass the required authorization information, you need to use http headers. There are many headers
you can set depending on the operation you want to run in CosmosDB. I am going
to cover only the required headers here.
In the following example, I am going to try to create a database in CosmosDB emulator by using the REST API. First let’s look at the required header fields for this request. These requirement applies to all other REST API calls too.
In the following example, I am going to try to create a database in CosmosDB emulator by using the REST API. First let’s look at the required header fields for this request. These requirement applies to all other REST API calls too.
x-ms-version : This is the version of the REST API. You can
find the available versions here. Which one to use? Try to use the latest one.
x-ms-date : This is the date of your request. It must be
formatted by Coordinated Universal Time. (ex: Mon, 18 Feb 2019 05:00:23 GMT)
x-ms-session-token: This is required if you want to use session consistency. For each of your new write request in Session consistency, CosmosDB assings a new SessionToken to the calls. You need to track the right session token and use it in this header property to keep usng the same session. SDK does this for you in the background, if you want to use the REST API, you need to do this manually. For this example, I am not going to worry about if I have the right session so I will ignore this one for the following example.
Authorization: This one is the most important and tricky
one. This needs to get generated for each of your call to Cosmos DB. It must be
in the following format
type={typeoftoken}&ver={tokenversion}&sig={hashedsignature}
You need to generate the hashedsignature by using the HTTP Verb, Resource Type, Resource Link and the Date. New line character (\n) is required between each value.
HTTP Verb : GET, POST, PUT, etc.
Resource Type : Type of CosmosDB object (Databases,
Collections, Documents, etc...)
ResourceLink : Which specific object type are your trying to
access/modify (a specific db, collection, document, etc..)
Date : Same value of x-ms-date. It must be in Coordinated
Universal Time.
sig = Verb.toLowerCase() + "\n" + ResourceType.toLowerCase() + "\n" + ResourceLink + "\n" + Date.toLowerCase() + "\n" + "" + "\n";
I wasn't kidding when I said it was tricky :) We are not
done yet. We still need to generate a hashed token from the signature.
Authorization string must be Base64 encoded using MIME RFC2045.To decode the
hashed token, we need to use master key using MIME RFC2045 as its Base64
encoded.
To call Cosmos DB directly from POSTMAN, first we need to
figure out what URL we need to use. Since I am using the emulator, I am going
to get the first part of the URL from the Emulator. This information will be in
QuickStart section. Mine is https://localhost:8081 as you can see in the following screenshot.
That was the first part of URL, now we need to figure out
the rest of the URL. For that, we need to look at the documentation of
CosmosDB Rest API. You can find all url locations from this link. Since I am
trying to create a database, I am going to use the following path.
Also, documentation tells us that this must be a POST Http Action.
In Postman, I pick the POST and type the URL to the URL section in the
following example.
Next, I want to create an environment in Postman to store
some variables. I need to generate a token for CosmosDB and get the current date to fill the header named x-ms-date. I am going to use variables in Environment to
store the values. To Create an environment. Click on gears icon and click on
Add.
Now, I want to create the following variables in the new Environment. I named the environment local.
masterKey : This is the key from the Emulator or Azure Cosmos DB
Portal. I am using my Cosmos DB Emulator so I really don't care about the master
key. Be very careful, If you want to do this in production. You don't want to
save your production masterkey to Postman or any unsecured location!
authToken : This will be the token we will create later to
pass CosmosDB
utcDate : This will be the required date in special format.
We will generate its value later.
We are ready to add values to headers section. Click on
Headers link, and add the following headers. These are the required HTTP headers for all CosmosDB REST API calls.
x-ms-date : {{utcDate}}
(This is the parameter we just created in POSTMAN Environment. We are going to generate its value in script.)
(This is the parameter we just created in POSTMAN Environment. We are going to generate its value in script.)
authorization : {{authToken}}
(This is the other parameter we just created. We are going to generate its value in script.)
(This is the other parameter we just created. We are going to generate its value in script.)
Content-Type: application/json.
(This is required since this is going to be a POST Http Action.)
Your screen should looks like this.
(This is required since this is going to be a POST Http Action.)
Your screen should looks like this.
Now, we need to generate an authorization token and the date
in the required format. To do that, I am going to use the Pre-request Script section.
POSTMAN will run this script before each request, This will be the part we will
generate authToken and utcDate parameters. Copy and Paste the code to the Pre-Request Script tab.
// Get the current date in UTCFormat and save it in parameter
var now = new Date().toUTCString();
pm.environment.set("utcDate", now);
// I need these to generate a token
var verb = 'POST';
var resourceType = "dbs";
var resourceId = '';
var text = (verb || "").toLowerCase() + "\n" + (resourceType || "").toLowerCase() + "\n" + (resourceId || "") + "\n" + now.toLowerCase() + "\n" + "" + "\n";
//Hash and Encode by using the masterkey.
var key = CryptoJS.enc.Base64.parse(pm.variables.get("masterKey"));
var signature = CryptoJS.HmacSHA256(text, key).toString(CryptoJS.enc.Base64);
var MasterToken = "master";
var TokenVersion = "1.0";
var authToken = encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
// Save it to parameter to use in HTTP header
pm.environment.set("authToken", authToken);
We are almost done, Only one thing left. I am trying to create a database, so I need to pass the name of the new database by using the body part of the request. You can find the required information in the documentation. In this example, all I need to pass is a JSON format document.
{ "id":"testdb" }
I think we are ready to send this request to local CosmosDB to create a new database. We have the URL, required headers, encoded signature and the body of the Request. I am going to click to Send and cross my fingers :)
Success! Look at all that information we received back in
the body of the Response. Database named testDb information is returned to me. Also, there are a lot of great information in headers tab too.
I think REST API and its features should definitely be applied while creating end report for users.
ReplyDeletepowerbi read rest
There was an error in evaluating the Pre-request Script: TypeError: Cannot read property 'length' of undefined
ReplyDeleteyou need to set the environment to "local" using the top right corner dropdown (which will say "No environment" if it's the same problem I had)
DeleteAlso, the variable names are case sensitive :)
DeleteThe problem in this line: var key = CryptoJS.enc.Base64.parse(pm.variables.get("masterKey"));
DeleteMethod "parse" get "undefined" from "pm.variables.get("masterKey")"
It turns out you must set a value to your variable before you want to read it.
pm.environment.set("masterKey", "Your primary ReadOnly(for example) key");
No, I've mistaken.
DeleteThe real problem is when you coping text from this page and inserting it to your environment when creating variables. You copy variable with EXCESS space, like this "masterKey " and trying to access it with the name "masterKey".
I tried same example but to query documents using select * from C. I see empty documents list retrieved. Container does have documents loaded. Am I missing something. Below are the headers am using
ReplyDeletex-ms-version:2018-12-31
x-ms-date: {{utcDate}}
authorization : {{authToken}}
Content-Type : application/query+json
Response recieved :
{
"_rid": "OkheAMiCxyY=",
"Documents": [],
"_count": 0
}
Thank you for sharing very useful blog!!!!
ReplyDeleteMicrosoft Azure DevOps training hyderabad
Microsoft Azure DevOps Training
Thank you for posting this. It helped me immensely!
ReplyDelete