page_type | languages | products | description | urlFragment | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
sample |
|
|
Creating API to securely access data using Row Level Security |
azure-sql-db-secure-data-access-api |
An API should allow its users to securely access the data in the database used by the API itself. At the same time it also must assure that data is protected and secured from those users who doesn't have enough authorization. This is even more important when creating multi-tenant applications.
Azure SQL offers an amazing feature to secure data at the database level, so that all the burden of taking care of such important and critical effort is done automatically by the database engine, so that the API code can be cleaner and easier to maintain and evolve. Not to mention better performances and improved efficiency as data will not leave the database at all, if the user has not the correct permissions.
This repo guides you to the creation of a API solution, deployable in Azure, that take advantage of Azure SQL Row Level Security to create secure API using Python, Flask and JWT. The same approach could be used with .NET or any other language that allows you to connect to Azure SQL.
A detailed video on how this sample work is available here:
The sample simulate an authenticated user by passing in the JWT token (that you'll generate using the pyjwt
tool) the hashed User Id. From a security point of view you want to make sure that a user can access only to his own data (or to the data s/he has been authorized to).
In order to run this sample, you need a Azure SQL database to use. If you already have one that can be used as a developer playground you can used that. Make sure create all the needed objects by executing the script:
./sql/00-SetupRLS.sql
Otherwise you can restore the rls_sample
database by using the
./sql/rls_sample.bacpac
. If you already know how to restore a database, great!, go on and once restore is done move on to next section. Otherwise, or if you want some scripts to help, use the following link:
If you need any help in executing the SQL script, you can find a Quickstart here: Quickstart: Use Azure Data Studio to connect and query Azure SQL database
Make sure you have Python 3.7 installed on your machine. Clone this repo in a directory on our computer and then create a virtual environment. For example:
virtualenv venv --python C:\Python37\
then activate the created virtual environment. For example, on Windows:
.\venv\Scripts\activate
and then install all the required packages:
pip install -r requirements
The connections string is not saved in the python code for security reasons, so you need to assign it to an environment variable in order to run the sample successfully. You also want to enable development environment for Flask:
Linux:
export FLASK_ENV="development"
export SQLAZURECONNSTR_RLS="<your-connection-string>"
Windows:
$Env:FLASK_ENV="development"
$Env:SQLAZURECONNSTR_RLS="<your-connection-string>"
Your connection string is something like:
DRIVER={ODBC Driver 17 for SQL Server};SERVER=<your-server-name>.database.windows.net;DATABASE=<your-database-name>;UID=MiddleTierUser;PWD=a987REALLY#$%TRONGpa44w0rd;
Just replace <your-server-name>
and <your-database-name>
with the correct values for your environment.
To run and test the Python REST API local, just run
flask run
Python will start the HTTP server and when everything is up and running you'll see something like
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Using a REST Client (like Insomnia, Postman or curl), you can now call your API. The API requires a Bearer Token that contains the Hashed User Id of the user you want to simulate:
User | Hashed Id |
---|---|
Jane Dean | 6134311589 |
John Doe | 1225328053 |
the definition of who can see what is stored in the rls.SensitiveDataPermissions
table.
To generate the Bearer Token you can use the pyjwt
that is automatically installed by the pyjwt
python package. Use the key mySUPERs3cr3t
to sign the JWT message.
Linux:
export token=`pyjwt --key=mySUPERs3cr3t encode iss=me exp=+600 user-hash-id=1225328053`
curl -s -H "Authorization: Bearer ${token}" -X GET http://localhost:5000/sensitive-data/more | jq .
Windows:
$token = pyjwt --key=mySUPERs3cr3t encode iss=me exp=+600 user-hash-id=1225328053
(Invoke-WebRequest -Uri http://localhost:5000/sensitive-data/more -Method GET -Headers @{"Authorization"="Bearer $token"}).Content
and you'll get info on Customer 123:
[
{
"Id": 1,
"FirstName": "Jane",
"LastName": "Dean",
"EvenMore": [...]
},
{
"Id": 2,
"FirstName": "John",
"LastName": "Doe",
"EvenMore": [...]
}
]
As you can see, data for both users is returned, even if you are invoking the API using a specific User. This is because the Row Level Security feature is disabled.
To enable to Row Level Security Policy execute the following code in the sample database:
alter security policy rls.SensitiveDataPolicy with (state = on)
If you try to access the same API again, you'll now see only the data for the user you are simulating:
[
{
"Id": 2,
"FirstName": "John",
"LastName": "Doe",
"EvenMore": [...]
}
]
Debugging from Visual Studio Code is fully supported. Make sure you create an .env
file the look like the following one (making sure you add your connection string)
FLASK_ENV="development"
SQLAZURECONNSTR_RLS=""
and you'll be good to go.
Now that your REST API solution is ready, it's time to deploy it on Azure so that anyone can take advantage of it. A detailed article on how you can that that is here:
- Deploying Python web apps to Azure App Services
- Quickstart: Create a Python app in Azure App Service on Linux
The only thing you have do in addition to what explained in the above articles is to add the connection string to the Azure Web App configuration. Using AZ CLI, for example:
appName="azure-sql-db-secure-data-access-api"
resourceGroup="my-resource-group"
az webapp config connection-string set \
-g $resourceGroup \
-n $appName \
--settings RLS=$SQLAZURECONNSTR_RLS \
--connection-string-type=SQLAzure
Just make sure you correctly set $appName
and $resourceGroup
to match your environment and also that the variable $SQLAZURECONNSTR_RLS
as also been set, as mentioned in section "Run sample locally". An example of a full script that deploy the REST API is available here: azure-deploy.sh
.
Please note that connection string are accessible as environment variables from Python when running on Azure, but they are prefixed as documented here:
https://docs.microsoft.com/en-us/azure/app-service/configure-common#connection-strings
That's why the Python code in the sample look for SQLAZURECONNSTR_RLS
but the Shell script write the RLS
connection string name.
To properly secure your API you need some kind of authentication. You can use Azure Active Directory:
Microsoft Authentication Library (MSAL) for Python
or some third party service like Auth0:
Here's some link if you want to learn more, or you are new to Python
REST API using Python, Flask and Azure SQL
If you want to do the same in .NET, this repo is a good start:
REST API using .Net, Dapper and Azure SQL
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.