├── .gitignore ├── CODE_OF_CONDUCT.md ├── Function ├── boostrapFunction │ ├── __init__.py │ ├── function.json │ └── sample.dat ├── extensions.csproj ├── host.json ├── local.settings.json ├── requirements.txt └── secureFlaskApp │ ├── __init__.py │ └── secureFlaskApp.wsgi ├── LICENSE ├── README.md ├── SECURITY.md └── images └── deployfunction.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | bin/ 24 | obj/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # Mac 109 | .DS_Store -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Function/boostrapFunction/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | import azure.functions as func 4 | 5 | from azf_wsgi import AzureFunctionsWsgi 6 | 7 | sys.path.insert(0,"./secureFlaskApp/") 8 | from secureFlaskApp import app as application 9 | 10 | def main(req: func.HttpRequest) -> func.HttpResponse: 11 | logging.info('Python HTTP trigger function processed a request.') 12 | return AzureFunctionsWsgi(application).main(req) -------------------------------------------------------------------------------- /Function/boostrapFunction/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptFile": "__init__.py", 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": [ 10 | "get", 11 | "post" 12 | ], 13 | "route": "{*route}" 14 | }, 15 | { 16 | "type": "http", 17 | "direction": "out", 18 | "name": "$return" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /Function/boostrapFunction/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /Function/extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | ** 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Function/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Function/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "FUNCTIONS_WORKER_RUNTIME": "python", 5 | "AzureWebJobsStorage": "{AzureWebJobsStorage}" 6 | } 7 | } -------------------------------------------------------------------------------- /Function/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | python-dotenv 3 | python-jose-cryptodome 4 | flask-cors 5 | six 6 | azf-wsgi 7 | -------------------------------------------------------------------------------- /Function/secureFlaskApp/__init__.py: -------------------------------------------------------------------------------- 1 | # from flask import Flask 2 | # app = Flask(__name__) 3 | # @app.route("/api") 4 | # def hello(): 5 | # print('******************* route hit') 6 | # return "Hello, Flask!" 7 | 8 | # if __name__ == "secureFlaskApp": 9 | # print('******************* init') 10 | # app.run() 11 | 12 | 13 | import json 14 | from six.moves.urllib.request import urlopen 15 | from functools import wraps 16 | 17 | from flask import Flask, request, jsonify, _request_ctx_stack 18 | from flask_cors import cross_origin 19 | from jose import jwt 20 | 21 | app = Flask(__name__) 22 | 23 | API_AUDIENCE = "https://funcapi..onmicrosoft.com/user_impersonation" 24 | TENANT_ID = "" 25 | 26 | # Error handler 27 | class AuthError(Exception): 28 | def __init__(self, error, status_code): 29 | self.error = error 30 | self.status_code = status_code 31 | 32 | @app.errorhandler(AuthError) 33 | def handle_auth_error(ex): 34 | print('handling error') 35 | response = jsonify(ex.error) 36 | response.status_code = ex.status_code 37 | return response 38 | 39 | # Format error response and append status code 40 | 41 | 42 | def get_token_auth_header(): 43 | """Obtains the Access Token from the Authorization Header 44 | """ 45 | auth = request.headers.get("Authorization", None) 46 | if not auth: 47 | raise AuthError({"code": "authorization_header_missing", 48 | "description": 49 | "Authorization header is expected"}, 401) 50 | 51 | parts = auth.split() 52 | 53 | if parts[0].lower() != "bearer": 54 | raise AuthError({"code": "invalid_header", 55 | "description": 56 | "Authorization header must start with" 57 | " Bearer"}, 401) 58 | elif len(parts) == 1: 59 | raise AuthError({"code": "invalid_header", 60 | "description": "Token not found"}, 401) 61 | elif len(parts) > 2: 62 | raise AuthError({"code": "invalid_header", 63 | "description": 64 | "Authorization header must be" 65 | " Bearer token"}, 401) 66 | 67 | token = parts[1] 68 | return token 69 | 70 | 71 | def requires_auth(f): 72 | """Determines if the Access Token is valid 73 | """ 74 | @wraps(f) 75 | def decorated(*args, **kwargs): 76 | try: 77 | token = get_token_auth_header() 78 | jsonurl = urlopen("https://login.microsoftonline.com/" + 79 | TENANT_ID + "/discovery/v2.0/keys") 80 | jwks = json.loads(jsonurl.read()) 81 | unverified_header = jwt.get_unverified_header(token) 82 | rsa_key = {} 83 | for key in jwks["keys"]: 84 | if key["kid"] == unverified_header["kid"]: 85 | rsa_key = { 86 | "kty": key["kty"], 87 | "kid": key["kid"], 88 | "use": key["use"], 89 | "n": key["n"], 90 | "e": key["e"] 91 | } 92 | except Exception: 93 | raise AuthError({"code": "invalid_header", 94 | "description": 95 | "Unable to parse authentication" 96 | " token."}, 401) 97 | if rsa_key: 98 | try: 99 | payload = jwt.decode( 100 | token, 101 | rsa_key, 102 | algorithms=["RS256"], 103 | audience=API_AUDIENCE, 104 | issuer="https://sts.windows.net/" + TENANT_ID + "/" 105 | ) 106 | except jwt.ExpiredSignatureError: 107 | raise AuthError({"code": "token_expired", 108 | "description": "token is expired"}, 401) 109 | except jwt.JWTClaimsError: 110 | raise AuthError({"code": "invalid_claims", 111 | "description": 112 | "incorrect claims," 113 | "please check the audience and issuer"}, 401) 114 | except Exception: 115 | raise AuthError({"code": "invalid_header", 116 | "description": 117 | "Unable to parse authentication" 118 | " token."}, 401) 119 | _request_ctx_stack.top.current_user = payload 120 | # print(_request_ctx_stack.top.current_user) 121 | return f(*args, **kwargs) 122 | raise AuthError({"code": "invalid_header", 123 | "description": "Unable to find appropriate key"}, 401) 124 | return decorated 125 | 126 | 127 | def requires_scope(required_scope): 128 | """Determines if the required scope is present in the Access Token 129 | Args: 130 | required_scope (str): The scope required to access the resource 131 | """ 132 | token = get_token_auth_header() 133 | unverified_claims = jwt.get_unverified_claims(token) 134 | if unverified_claims.get("scope"): 135 | token_scopes = unverified_claims["scope"].split() 136 | for token_scope in token_scopes: 137 | if token_scope == required_scope: 138 | return True 139 | return False 140 | 141 | # Controllers API 142 | 143 | # This doesn't need authentication 144 | @app.route("/public") 145 | @cross_origin(headers=['Content-Type', 'Authorization']) 146 | def public(): 147 | response = "Public endpoint - open to all" 148 | return jsonify(message=response) 149 | 150 | # This needs authentication 151 | @app.route("/api") 152 | @cross_origin(headers=['Content-Type', 'Authorization']) 153 | @requires_auth 154 | def private(): 155 | return jsonify(message=_request_ctx_stack.top.current_user) 156 | 157 | if __name__ == '__main__': 158 | app.run() 159 | -------------------------------------------------------------------------------- /Function/secureFlaskApp/secureFlaskApp.wsgi: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0,"./secureFlaskApp/") 3 | from secureFlaskApp import app as application -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - python 5 | products: 6 | - azure 7 | - azure-active-directory 8 | name: Python Azure Function Web API secured by Azure AD 9 | description: Python Azure Function Web API secured by Azure AD. 10 | urlFragment: "ms-identity-python-webapi-azurefunctions" 11 | --- 12 | 13 | # Python Azure Function Web API secured by Azure AD 14 | 15 | This code example demonstrates how to secure an Azure Function with Azure AD when the function uses HTTPTrigger and exposes a Web API. The Web API is written using python. 16 | 17 | This readme walks you through the steps of setting this code up in your Azure subscription. 18 | 19 | While you can develop Azure Functions in many ways, such as Visual Studio 2019, Visual Studio Code, etc. this guide shows how to perform the steps using Visual Studio Code. 20 | 21 | ## Contents 22 | 23 | Outline the file contents of the repository. It helps users navigate the codebase, build configuration and any related assets. 24 | 25 | | File/folder | Description | 26 | |-------------------|--------------------------------------------| 27 | | `src` | Sample source code. | 28 | | `.gitignore` | Define what to ignore at commit time. | 29 | | `CHANGELOG.md` | List of changes to the sample. | 30 | | `CONTRIBUTING.md` | Guidelines for contributing to the sample. | 31 | | `README.md` | This README file. | 32 | | `LICENSE` | The license for the sample. | 33 | | `images` | Images used in readme.md. | 34 | | `Function` | The Azure Function code. | 35 | 36 | ## Prerequisites 37 | 1. You must have Visual Studio Code installed 38 | 2. You must have Azure Functions core tools installed `npm install -g azure-functions-core-tools` 39 | 3. Azure Functions VSCode extension (https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) 40 | 41 | ## Register an AAD App 42 | 43 | Reference: [How to register an app](https://docs.microsoft.com/en-nz/azure/active-directory/develop/quickstart-register-app) 44 | 45 | The Azure Function acts as a WebAPI. There are a few things to know here. 46 | 1. The function app will run on `http://localhost:3000` when you test it locally. 47 | 2. The function app will run on `https://.azurewebsites.net` when you run it deployed in azure 48 | 3. The function exposes an API with app id uri `https://..onmicrosoft.com` 49 | 50 | Note that all these values are configurable to your liking, and they are reflected in the `Function/secureFlaskApp/__init__.py` file. 51 | 52 | Additionally, you will need a "client" for the Web API. Since this function will serve as a AAD protected Web API, any client that understands standard openid connect flows will work. The usual consent grant principals apply. 53 | 54 | Reference: [Azure Active Directory consent framework](https://docs.microsoft.com/en-us/azure/active-directory/develop/consent-framework) 55 | 56 | To keep things simple, we will reuse the same app registration as both the client, and the API. This eliminates any need to provide explicit consent. For our code example here, the client will use [auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow), for which we will also need a client secret. We are trying to mimic a web app calling this API, and a web app can act as a confidential client. 57 | 58 | To setup the app, you can use the below azure CLI script. Note the placeholders demarcated in `<..>` brackets. Make sure to replace them with your environment specific values. 59 | 60 | ``` SHELL 61 | az ad app create --display-name "FuncAPI" --credential-description "funcapi" --password "p@ssword1" --reply-urls "http://localhost:3000" --identifier-uris "https://funcapi..onmicrosoft.com" 62 | ``` 63 | 64 | For the above registered app, get the app ID 65 | ``` SHELL 66 | az ad app list --query "[?displayName == 'FuncAPI'].appId" 67 | ``` 68 | 69 | Also get your tenant ID 70 | ``` SHELL 71 | az account show --query "tenantId" 72 | ``` 73 | 74 | Update your `Function/secureFlaskApp/__init__.py` with the values per your app registration. Specifically, update the below lines. 75 | 76 | ``` Python 77 | API_AUDIENCE = "https://funcapi..onmicrosoft.com/user_impersonation" 78 | TENANT_ID = "" 79 | ``` 80 | 81 | ## Test your function - locally 82 | 83 | 1. With the project open in VSCode, just hit F5, or you can also run `func host start` from the CLI. 84 | 2. You will need an access token to call this function. In order to get the access token, open browser in private mode and visit 85 | ``` 86 | https://login.microsoftonline.com/.onmicrosoft.com/oauth2/v2.0/authorize?response_type=code&client_id=&redirect_uri=http://localhost:3000/&scope=openid 87 | ``` 88 | 89 | This will prompt you to perform authentication and consent, and it will return a code in the query string. 90 | Use that code in the following request to get an access token, remember to put in the code and client secret. 91 | I am using the client secret of `p@ssword1` as I setup in my scripts above. In production environments, you want this to be more complex. 92 | 93 | ``` SHELL 94 | curl -X POST \ 95 | https://login.microsoftonline.com/.onmicrosoft.com/oauth2/v2.0/token \ 96 | -H 'Accept: */*' \ 97 | -H 'Cache-Control: no-cache' \ 98 | -H 'Connection: keep-alive' \ 99 | -H 'Content-Type: application/x-www-form-urlencoded' \ 100 | -H 'Host: login.microsoftonline.com' \ 101 | -H 'accept-encoding: gzip, deflate' \ 102 | -H 'cache-control: no-cache' \ 103 | -d 'redirect_uri=http%3A%2F%2Flocalhost:3000&client_id=&grant_type=authorization_code&code=&client_secret=p@ssword1&scope=https%3A%2F%funcapi..onmicrosoft.com%2F/user_impersonation' 104 | ``` 105 | 106 | 3. Once you get the access token, make a GET request to `http://localhost:3000/api` with the access token as a Authorization Bearer header. Verify that you get an output similar to the below. The values marked as ..removed.. will have actual values in your output. 107 | 108 | ``` JSON 109 | { 110 | "aud": "https://funcapi..onmicrosoft.com", 111 | "iss": "https://sts.windows.net//", 112 | "iat": 1571732525, 113 | "nbf": 1571732525, 114 | "exp": 1571736425, 115 | "acr": "1", 116 | "aio": "..removed..", 117 | "amr": [ 118 | "pwd" 119 | ], 120 | "appid": "..removed..", 121 | "appidacr": "1", 122 | "email": "..removed..", 123 | "family_name": "..removed..", 124 | "given_name": "..removed..", 125 | "idp": "..removed..", 126 | "ipaddr": "..removed..", 127 | "name": "..removed..", 128 | "oid": "..removed..", 129 | "scp": "user_impersonation", 130 | "sub": "..removed..", 131 | "tid": "..removed..", 132 | "unique_name": "..removed..", 133 | "uti": "..removed..", 134 | "ver": "1.0" 135 | } 136 | ``` 137 | 138 | ## Test your function - in Azure 139 | 140 | 1. Go ahead and create a function app in azure, ensure that you pick python as it's runtime. 141 | 2. Choose to deploy the function 142 | 143 | ![Deploy Function](images/deployfunction.png) 144 | 145 | 3. You will need an access token to call this function. In order to get the access token, open browser in private mode and visit 146 | ``` 147 | https://login.microsoftonline.com/.onmicrosoft.com/oauth2/v2.0/authorize?response_type=code&client_id=&redirect_uri=https://.azurewebsites.net/callback&scope=openid 148 | ``` 149 | 150 | This will prompt you to perform authentication, and it will return a code. 151 | Use that code in the following request to get an access token, remember to put in the code and client secret. 152 | 153 | ``` SHELL 154 | curl -X POST \ 155 | https://login.microsoftonline.com/.onmicrosoft.com/oauth2/v2.0/token \ 156 | -H 'Accept: */*' \ 157 | -H 'Cache-Control: no-cache' \ 158 | -H 'Connection: keep-alive' \ 159 | -H 'Content-Type: application/x-www-form-urlencoded' \ 160 | -H 'Host: login.microsoftonline.com' \ 161 | -H 'accept-encoding: gzip, deflate' \ 162 | -H 'cache-control: no-cache' \ 163 | -d 'redirect_uri=https%3A%2F%2F.azurewebsites.net%2Fcallback&client_id=&grant_type=authorization_code&code=&client_secret=&scope=https%3A%2F%2Fmytestapp..onmicrosoft.com%2Fuser_impersonation' 164 | ``` 165 | 166 | 3. Once you get the access token, make a GET request to `https://.azurewebsites.net/api` with the access token as a Authorization Bearer header. Verify that you get an output similar to the below. The values marked as ..removed.. will have actual values in your output. 167 | 168 | ``` JSON 169 | { 170 | "aud": "https://funcapi..onmicrosoft.com", 171 | "iss": "https://sts.windows.net//", 172 | "iat": 1571732525, 173 | "nbf": 1571732525, 174 | "exp": 1571736425, 175 | "acr": "1", 176 | "aio": "..removed..", 177 | "amr": [ 178 | "pwd" 179 | ], 180 | "appid": "..removed..", 181 | "appidacr": "1", 182 | "email": "..removed..", 183 | "family_name": "..removed..", 184 | "given_name": "..removed..", 185 | "idp": "..removed..", 186 | "ipaddr": "..removed..", 187 | "name": "..removed..", 188 | "oid": "..removed..", 189 | "scp": "user_impersonation", 190 | "sub": "..removed..", 191 | "tid": "..removed..", 192 | "unique_name": "..removed..", 193 | "uti": "..removed..", 194 | "ver": "1.0" 195 | } 196 | ``` 197 | 198 | ## Contributing 199 | 200 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 201 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 202 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 203 | 204 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 205 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 206 | provided by the bot. You will only need to do this once across all repos using our CLA. 207 | 208 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 209 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 210 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 211 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /images/deployfunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-python-webapi-azurefunctions/83a67bd97d6a97f2094b3044a40879dbc1ef6feb/images/deployfunction.png --------------------------------------------------------------------------------