├── .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 | 
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
--------------------------------------------------------------------------------