Git Product home page Git Product logo

python-sample-auth's Introduction

Python authentication samples for Microsoft Graph

language:Python license:MIT

To make calls to Microsoft Graph, your app must obtain a valid access token from Azure Active Directory (Azure AD), the Microsoft cloud identity service, and the token must be passed in an HTTP header with each call to the Microsoft Graph REST API. You can acquire access tokens via industry-standard OAuth 2.0 and Open ID Connect protocols, and use an Azure Active Directory v2.0 authentication library to implement those protocols.

This repo includes examples of four different approaches you can use to authenticate with Azure AD from a Python web application. Each sample implements the OAuth 2.0 Authorization Code Grant workflow, which is the recommended approach for web applications written in Python.

Sample architecture

The samples in this repo all do the same thing: prompt the user to log on, and then display their user profile data as JSON. All samples use the same names for variables, functions, and routes, and they also use the same HTML templates and CSS, to make it easy to see how the implementation details vary between different auth libraries.

The following diagram shows how each sample implements the Authorization Code Grant workflow.

authentication workflow

Each sample_*.py source file has the same structure:

  1. initial setup — Read configuration settings and instantiate auth provider.
  2. homepage() — Static page with a /login button.
  3. login() — Call auth provider to authenticate user, Azure AD returns authorization code.
  4. authorize() (Redirect URI) — Use authorization code to request/save token, redirect to /graphcall.
  5. graphcall() — Query Microsoft Graph and display returned data.

You can modify the samples to test specific Microsoft Graph calls you'd like to make by changing the endpoint, and changing the requested permissions to what that endpoint requires. For example, to retrieve your email messages instead of user profile data, change the /me endpoint to /me/messages and add Mail.Read to the list of permissions requested in the SCOPES setting of config.py. With those changes, the sample will display a JSON document that contains the top ten messages from your mailbox.

Note that these samples are intended to clarify the minimum steps required for authenticating and making calls to Microsoft Graph. They don't include error handling and other common practices for production deployment.

Python auth options

The following is a summary of the authentication options that the code samples in this repo demonstrate.

Microsoft ADAL (sample_adal.py)

The sample_adal.py sample shows how to use the Microsoft Azure Active Directory Authentication Library (ADAL) for Python for authentication to Microsoft Graph. ADAL supports a variety of token acquisition methods and can be used for other Azure AD authentication scenarios in addition to working with Microsoft Graph. ADAL does not provide support for Microsoft Accounts or incremental consent. If you need those capabilities, one of the other options might be a better fit.

In addition to sample_adal.py, which uses the Flask web framework, a sample_adal_bottle.py version is provided, which uses the Bottle web framework.

Flask-OAuthlib (sample_flask.py)

If you're building a Flask-based web application, the Flask-OAuthlib provides a simple way to authenticate with Azure AD for Microsoft Graph. The sample_flask.py sample shows how to use Flask-OAuthlib to authenticate to Microsoft Graph.

Request-OAuthlib (sample_requests.py)

If you're using Requests, the most popular HTTP library for Python developers, Requests-OAuthlib is a good option for Microsoft Graph authentication. The sample_requests.py sample shows how to use Requests-OAuthlib to authenticate to Microsoft Graph from a Bottle web app.

graphrest module (sample_graphrest.py)

If you're interested in developing your own authentication module, or are curious about the details of implementing OAuth 2.0 authentication for a web application, the sample_graphrest.py sample provides an example of authenticating with graphrest, a custom auth library written in Python. Note that this sample uses the Bottle web framework, although it is relatively easy to port it to Flask or any other web framework that supports redirects and provides access to request query parameters.

Running the samples

To install and configure the samples in this repo, see the instructions in Installing the Python authentication samples. These samples only require the User.Read permission, which is the default, so you don't need to specify additional permissions while registering the application.

After you've completed those steps, follow these steps to run the samples:

  1. To start a sample, run the command python <progname> in the root folder of the cloned repo. For example, to run the ADAL sample, use this command: python sample_adal.py.

  2. Go to this URL in a browser: http://localhost:5000. You should see a home page like this:

    home page

  3. Choose Connect, and then select your Microsoft account or Office 365 account and follow the instructions to log on. The first time you log on to the app under a particular identity, you will be prompted to consent to the permissions that the app is requesting. Choose Accept, which gives the application permission to read your profile information. You'll then see the following screen, which shows that the app has successfully authenticated and is able to read your profile information from Microsoft Graph:

sample output

Python package dependencies

The requirements.txt file for this repo includes all of the packages for all of the auth samples. If you only plan to use one of the samples, you may prefer to only install the packages required for that sample. The following table lists the Python package dependencies for each sample.

Sample Auth Library Dependencies
sample_adal.py Microsoft ADAL
  • adal
  • requests
  • flask
sample_flask.py Flask-OAuthlib
  • flask
  • flask-oauthlib
sample_requests.py Requests-OAuthlib
  • requests
  • requests-oauthlib
  • bottle
sample_graphrest.py graphrest module
  • requests
  • bottle

Contributing

These samples are open source, released under the MIT License. Issues (including feature requests and/or questions about this sample) and pull requests are welcome. If there's another Python sample you'd like to see for Microsoft Graph, we're interested in that feedback as well — please log an issue and let us know!

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Resources

Documentation:

Samples:

Auth libraries:

Specifications:

python-sample-auth's People

Contributors

jackson-woods avatar jasonjoh avatar lauragra avatar nokafor avatar supernova-eng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-sample-auth's Issues

It appears that after token expires error is reported

{'error': {'code': 'InvalidAuthenticationToken', 'message': 'CompactToken validation failed with reason code: 80049228.', 'innerError': {'request-id': 'c2920885-4c71-4dea-bee7-dbe21c52ec1e', 'date': '2018-08-02T21:52:02'}}}

SSL error using sample_flask.py

Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 2309, in __call__ return self.wsgi_app(environ, start_response) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 2295, in wsgi_app response = self.handle_exception(e) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 1741, in handle_exception reraise(exc_type, exc_value, tb) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise raise value File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app response = self.full_dispatch_request() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request rv = self.handle_user_exception(e) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception reraise(exc_type, exc_value, tb) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise raise value File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request rv = self.dispatch_request() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "/Users/ml/Desktop/python-sample-auth/sample_flask.py", line 39, in authorized response = MSGRAPH.authorized_response() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask_oauthlib/client.py", line 707, in authorized_response data = self.handle_oauth2_response(args) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask_oauthlib/client.py", line 671, in handle_oauth2_response method=self.access_token_method, File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask_oauthlib/client.py", line 404, in http_request resp = http.urlopen(req) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 223, in urlopen return opener.open(url, data, timeout) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 526, in open response = self._open(req, data) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 544, in _open '_open', req) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 504, in _call_chain result = func(*args) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 1361, in https_open context=self._context, check_hostname=self._check_hostname) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/request.py", line 1320, in do_open raise URLError(err) urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)>

authorization request denied

Hi,

I run the sample_adal.py to play around with the AAD. However, after authorization, I got following when I was transferred to the graphcall path.

{'error': {'code': 'Authorization_RequestDenied', 'message': 'Insufficient privileges to complete the operation.', 'innerError': {'request-id': '15038357-2dee-45b7-9d84-a3adae7b7c47', 'date': '2018-12-12T21:43:28'}}}

Could someone please give me some hint about how to solve it?

Best regards

flask.session empty

Python 3.7 with pip install requirements on flask-oauthlib (note there is currently a conflict with the request-oauthlib)

I am running the sample_flask.py as is.
My config.py is updated with an App registration from Azure.
The App registration is:

  • type: Web app / API
  • reply urls includes http://localhost:5000/login/authorized
  • Required permission > Microsoft Graph > Sign in and read user profile

My config file has

CLIENT_ID = [ID from app registration above]
CLIENT_SECRET = [secret key from app registration]
REDIRECT_URI = 'https://localhost:5000/login/authorized'
AUTHORITY_URL = 'https://login.microsoftonline.com/[authority specific to my company]'
AUTH_ENDPOINT = '/oauth2/v2.0/authorize'
TOKEN_ENDPOINT = '/oauth2/v2.0/token'
RESOURCE = 'https://graph.microsoft.com/'
API_VERSION = 'v1.0'
SCOPES = ['User.Read'] 

The webserver starts up fine, I click login, it goes through the redirect, brings me to my organization page, credentials work fine, and then the redirect comes back to this function (sample_flask.py):

@APP.route('/login/authorized')
def authorized():
    """Handler for the application's Redirect Uri."""
    if str(flask.session['state']) != str(flask.request.args['state']):
        raise Exception('state returned to redirect URL does not match!')
    response = MSGRAPH.authorized_response()
    flask.session['access_token'] = response['access_token']
    return flask.redirect('/graphcall')

I can see that the flask.request.args['state'] is correct and includes the state but the flask.session is now empty and causes this error:

  File "/Users/vincent/repos/python-sample-auth/sample_flask.py", line 37, in authorized
    if str(flask.session['state']) != str(flask.request.args['state']):
  File "/anaconda3/envs/flask-auth/lib/python3.7/site-packages/werkzeug/local.py", line 377, in <lambda>
    __getitem__ = lambda x, i: x._get_current_object()[i]
  File "/anaconda3/envs/flask-auth/lib/python3.7/site-packages/flask/sessions.py", line 84, in __getitem__
    return super(SecureCookieSession, self).__getitem__(key)
KeyError: 'state'

Somehow the session object gets emptied. If I print out flask.session within the authorized function it is an empty <SecureCookieSession {}>

Redirect url?

The installation instructions stipulate:

  1. Enter http://localhost:5000/login/authorized as the Redirect URL, and then choose Save.

but I only get the following three options:

  1. home page url
  2. conditions url
  3. privacy url

I tried to home page url but this did not work.

Am I missing something?
Thanks

A post call to add a new openTypeExtension to user data fails with "Resource not found for the segment 'extensions'"

I've modified sample_flask.py by adding a new action to the graphcall function. The new MSGRAPH.post call should create an openTypeExtension to the user. The code is as follows:

endpoint = 'me/extensions'
data = {
    "@odata.type": "microsoft.graph.openTypeExtension",
    "extensionName": "com.myorgnamehere.dashboard",
    "permissions": "guest"
}
graphanswer = MSGRAPH.post(endpoint, headers=headers, data=data).data
print(graphanswer)

with the complete endpoint path being "https://graph.microsoft.com/v1.0/me/extensions", and I've tried using the absolute uid path as well. Headers are recycled from the get example in the source. Either case results in the following error:

{'error': 
    {'code': 'RequestBroker--ParseUri', 
    'message': "Resource not found for the segment 'extensions'.", 
    'innerError': 
        {'request-id': '<omitted>', 'date': '2019-11-20T23:37:40'}
    }
}

I've checked the token in jwt.ms for the necessary scopes, which I've elevated to User.ReadWrite.All and granted permissions. When I test the post method in Graph Explorer, everything pans out very nicely and the extension is created.

Is there an obvious error here? I've been reading through everything related to "resource not found" errors and openTypeExtension, but haven't found anything pertinent thus far.

[sample_flask.py] ImportError: Cannot import name from 'werkzeug'

image

This occurred on Python 3.6 and 3.7.

Looks like flask_oauthlib has been deprecated and has changed to authlib so no point reporting this issue on that repo.

However, I resolved this error by including the specific modules in \Python3x\Lib\site-packages\flask_oauthlib\client.py:

from werkzeug.urls import url_quote, url_decode, url_encode
from werkzeug.http import parse_options_header
from werkzeug.utils import cached_property

This was the only roadblock I experienced using this example - hope it helps someone.

Are there any methods of authing that dont require flask?

I am writing an Alexa skill that runs in aws lambda. I haven't tested this, but I don't believe flask will work very well there.

When you auth via PS you simply make rest requests, im not sure why almost every method of authing in python reference flask.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.