Access Sharepoint Online With Entra Registration

At work, I was recently asked to allow an on-premise Python application access to SharePoint Online (SPO).

The now-depreciated way would be to go to your SPO site, append /_layouts/15/appregnew.aspx to the site's name (so it would look something like https://tenant.sharepoint.com/sites/TestSiteforPythonAPIAccess/_layouts/15/appregnew.aspx). This is creating an app registration on the specified SPO site, but this method is being depricated: https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/retirement-announcement-for-add-ins. It works in my production tenant, but fails in my developer space.

My user didn't specify how they intended to authenticate to SPO, so with a little bit of digging, I find https://github.com/vgrem/Office365-REST-Python-Client as a seemingly well supported package that handles this from Python.

Interactive Authentication

If the tool we're using only requires interactive authentication, then this becomes relatively easy:

  1. Create an App Registration in Entra. Call it whatever you want. Collect the "Application (client) ID" and "Directory (tenant) ID" for later:
    App Registration overview

And.. that's it. App Registrations come with a default Graph User.Read right, which is all we need at this point.

  1. Add the user(s) to the SPO site. This is where we're manging the access to the resources for the users.

  2. Proft? My test Python is pretty minimal:

1test_site_url = "https://tenant.sharepoint.com/sites/TestSiteforPythonAPIAccess"
2test_tenant = "b59ed669-e556-4c24-xxxx-xxxxxxxxxxxx"
3test_client_id = "0e8f0030-f25d-4ba9-ae85-415ebbd2dda3"
4
5ctx = ClientContext(test_site_url).with_interactive(test_tenant, test_client_id)
6me = ctx.web.current_user.get().execute_query()
7print(me)
8web = ctx.web.get().execute_query()
9print(web)

If we did our stuff right, then thisn should print out my name, and the name of the SPO site.

Unattended Interaction

This gets a bit more spicy.

  1. Generate a self-signed certificiate that you will use for authentication. Something like openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out selfsigncert.crt, followed by cat selfsigncert.crt privateKey.key > selfsigncert.pem should do the trick.
  2. In your Entra App Registration, under Manage, Certificates & Secrets, upload the created selfsigncert.pem in the Certificates tab:
    Adding a certificate to the App Registration
  3. Collect the Thumbprint from this view (though we can get it with openssl: openssl x509 -in selfsignedcert.pem -noout -fingerprint)
  4. Still in the App Registration, under API Permissions, add SharePoint, Application, Sites.Selected. Grant Admin consent:
    API Permissions
    There is a Graph Sites.Selected API as well. This does not work with the Python library selected.
  5. Collect the Sharepoint Site ID from your site: https://tenant.sharepoint.com/sites/TestSiteforPythonAPIAccess/_api/site/id. Look for the XML element with type type of Edm.Guid. Collect this too.

To summarize, we need:

  • The SPO Site ID
  • The Entra App Registration Client ID
  • The Entra App Registration name
  • The Entra Tenant ID
  • Certificate PEM
  • Certificate thumbprint
  1. Head over to the MS Graph Explorer and log in as a user with Global Admin access (or I think Site collection Admin).
  2. Query GET to https://graph.microsoft.com/v1.0/sites/<SPO Site ID>/permissions. You should get a response, with an empty value:
    Graph Explorer for SPO site
    If you do NOT get a 200 response code, you probably want to address the error first. Check:
  • Are you logged in? If not, you are probably querying a default set of data
  • Have you specified the Site ID in the URL?
  1. Compose a POST to the same URL with the following JSON:
 1{
 2    "roles": [
 3        "fullcontrol"
 4    ],
 5    "grantedToIdentities": [
 6        {
 7            "application": {
 8                "id": "0e8f0030-f25d-4ba9-xxxx-xxxxxxxxxxx",
 9                "displayName": "PythonTest"
10            }
11        }
12    ]
13}

Replace the app.id and app.displayName with the real information from the Entra App Registration. For some reason, you must have the displayName.

  1. Re-run the GET you did in step 7. Hopefully you see your granted role.

Steps 6 through 9 can also be accomodated with the PnP.PowerShell modules. This is arguably a bit more user-friendly, but takes a bit more to setup. See https://pnp.github.io/powershell/cmdlets/Connect-PnPOnline.html, which will also need https://pnp.github.io/powershell/articles/registerapplication.html (if you have not already set up PnP in your tenant) and PowerShell 7.

Anyhow. On to test.

 1test_site_url = "https://tenant.sharepoint.com/sites/TestSiteforPythonAPIAccess"
 2test_tenant = "b59ed669-e556-4c24-xxxx-xxxxxxxxxxxx"
 3test_client_id = "0e8f0030-f25d-4ba9-ae85-415ebbd2dda3"
 4
 5cert_settings = {
 6    'tenant': test_tenant,
 7    'client_id': test_client_id, 
 8    'thumbprint': "EDC90C2DA540BD925F784F7C9C6CA062411A20C6",
 9    'cert_path': "{0}/selfsignedcert.pem".format(os.path.dirname(__file__)),
10}
11
12ctx = ClientContext(test_site_url).with_client_certificate(**cert_settings)
13
14current_web = ctx.web
15ctx.load(current_web)
16ctx.execute_query()
17print("{0}".format(current_web.url))
18
19me = ctx.web.current_user.get().execute_query()
20print(me)
21web = ctx.web.get().execute_query()
22print(web)

Since we're not running as an identified user now, you should see an output of the current web URL, the user as "SharePoint App", and the site name.

Change the test site URL to another SPO site, and verify that you get a 403.