Skip to content

Commit d9af073

Browse files
authored
Merge pull request #1018 from vgrem/feat/saml-migration-guide
feat: add SAML-to-certificate migration guide
2 parents 054b8c4 + 217d54d commit d9af073

2 files changed

Lines changed: 148 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pip install git+https://github.com/vgrem/office365-rest-python-client.git
6363
> Legacy flows (ACS, SAML) are fully retired as of April/May 2026.
6464
>
6565
> [ACS retirement notice](https://aka.ms/retirement/acs/support) | [SAML retirement MC1184649](https://learn.microsoft.com/en-us/sharepoint/dev/security/saml-auth-retirement)
66+
>
67+
> Migrating from SAML? See [examples/sharepoint/auth/modern/migrate_from_saml_to_cert.py](examples/sharepoint/auth/modern/migrate_from_saml_to_cert.py)
6668
6769
Two clients, different auth capabilities:
6870

@@ -81,7 +83,7 @@ Two clients, different auth capabilities:
8183
| **Device code** | `with_device_flow(tenant, client_id)` | Delegated || ✅ Supported | [Docs](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code) |
8284
| **NTLM** (on-prem only) | `with_user_credentials(username, password)` | Delegated || ✅ On-prem only | [Example](examples/sharepoint/auth/legacy/with_ntlm.py) |
8385
| **Client secret** (Azure AD) | `with_client_secret(tenant, client_id, secret)` | App-only || ❌ Not documented | [Microsoft's own migration guidance](https://mc.merill.net/message/MC1184649) confirms: replace legacy ACS auth with OAuth2 client credentials flow using certificates, not client secrets, for SharePoint access. |
84-
| ~~SAML user auth~~ | `with_user_credentials(username, password)` | Delegated || 🚫 Retired May 2026 | [MC1184649](https://learn.microsoft.com/en-us/sharepoint/dev/security/saml-auth-retirement) |
86+
| ~~SAML user auth~~ | `with_user_credentials(username, password)` | Delegated || 🚫 Retired May 2026 | [Migration guide](examples/sharepoint/auth/modern/migrate_from_saml_to_cert.py) |
8587
| ~~ACS app-only~~ | `with_credentials(ClientCredential(...))` | App-only || 🚫 Retired Apr 2026 | [Notice](https://aka.ms/retirement/acs/support) |
8688

8789
### GraphClient — Microsoft Graph auth
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""
2+
Migrate from legacy SAML auth to Azure AD certificate auth.
3+
4+
Microsoft retired SAML/WS-Federation for SharePoint Online in May 2026
5+
(MC1184649). The old with_user_credentials() method no longer works
6+
for SharePoint Online.
7+
8+
This example covers the migration path to Azure AD app-only with a
9+
certificate, which is the recommended replacement.
10+
11+
Prerequisites:
12+
- An app registered in Azure AD (or update an existing one)
13+
- openssl (for certificate generation)
14+
- Admin consent for SharePoint API permissions
15+
16+
See https://learn.microsoft.com/en-us/sharepoint/dev/security/saml-auth-retirement
17+
See https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread
18+
"""
19+
20+
# ===========================================================================
21+
# Step 1 -- Generate a self-signed certificate (run in terminal)
22+
# ===========================================================================
23+
#
24+
# # Create private key and self-signed certificate (365-day validity)
25+
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
26+
# -keyout private_key.pem -out cert.pem \
27+
# -subj "/CN=SharePointApp"
28+
#
29+
# # Extract thumbprint (needed below)
30+
# openssl x509 -in cert.pem -fingerprint -noout \
31+
# | sed 's/.*=//' | sed 's/://g'
32+
#
33+
# # Alternatively, convert an existing PFX from Azure:
34+
# openssl pkcs12 -in myapp.pfx -nocerts -nodes \
35+
# | openssl pkcs8 -topk8 -nocrypt -out private_key.pem
36+
# openssl pkcs12 -in myapp.pfx -nokeys -out cert.pem
37+
38+
# ===========================================================================
39+
# Step 2 -- Register / update an app in Azure AD
40+
# ===========================================================================
41+
#
42+
# Option A -- Create a new app registration:
43+
# 1. Go to https://entra.microsoft.com/ -> App registrations -> New registration
44+
# 2. Name: e.g. "SharePoint Python Client"
45+
# 3. Supported account types: "Accounts in this organizational directory only"
46+
# 4. Do not set a redirect URI (this is app-only)
47+
# 5. Click Register and note the Application (client) ID and Directory (tenant) ID
48+
#
49+
# Option B -- Update an existing app:
50+
# 1. Same app works -- just add a certificate credential under "Certificates & secrets"
51+
# 2. No need to create a new app registration
52+
53+
# ===========================================================================
54+
# Step 3 -- Grant SharePoint API permissions
55+
# ===========================================================================
56+
#
57+
# IMPORTANT: Grant SharePoint permissions, not Microsoft Graph permissions.
58+
# ClientContext uses the SharePoint REST API, which requires SharePoint
59+
# resource permissions.
60+
#
61+
# 1. In your app registration -> "API permissions"
62+
# 2. Click "Add a permission" -> "APIs my organization uses"
63+
# 3. Search for and select "SharePoint"
64+
# 4. Select "Application permissions"
65+
# 5. Choose at minimum: Sites.Read.All or Sites.FullControl.All
66+
# 6. Click "Add permissions"
67+
# 7. Click "Grant admin consent for [tenant]" and confirm
68+
#
69+
# Verify that "SharePoint" (not "Microsoft Graph") appears among configured
70+
# permissions.
71+
72+
# ===========================================================================
73+
# Step 4 -- Upload the certificate to the app registration
74+
# ===========================================================================
75+
#
76+
# 1. In your app registration -> "Certificates & secrets"
77+
# 2. Click "Upload certificate"
78+
# 3. Select the cert.pem file from Step 1
79+
# 4. Click "Add"
80+
# 5. Copy the Thumbprint value (hex string, no colons)
81+
82+
# ===========================================================================
83+
# Step 5 -- Connect and verify
84+
# ===========================================================================
85+
86+
from office365.sharepoint.client_context import ClientContext
87+
from tests import test_client_id, test_site_url, test_tenant
88+
89+
site_url = test_site_url
90+
tenant = test_tenant
91+
client_id = test_client_id
92+
thumbprint = "AABBCCDDEEFF00112233445566778899AABBCCDD"
93+
94+
ctx = ClientContext(site_url).with_client_certificate(
95+
tenant=tenant,
96+
client_id=client_id,
97+
thumbprint=thumbprint,
98+
cert_path="./private_key.pem",
99+
)
100+
101+
web = ctx.web.get().execute_query()
102+
print("Connected to: {0}".format(web.url))
103+
print("Site title: {0}".format(web.title))
104+
105+
# ===========================================================================
106+
# What changed from the old SAML approach
107+
# ===========================================================================
108+
#
109+
# OLD (retired -- do not use):
110+
# ctx = ClientContext(url).with_user_credentials(username, password)
111+
#
112+
# NEW (recommended):
113+
# ctx = ClientContext(url).with_client_certificate(
114+
# tenant, client_id, thumbprint, cert_path
115+
# )
116+
#
117+
# Benefits of certificate auth over SAML:
118+
# - No username/password to rotate
119+
# - No MFA or conditional access issues (app-only)
120+
# - Aligned with Microsoft's long-term auth strategy
121+
# - Works with Sovereign clouds (GCC High, 21Vianet, etc.)
122+
123+
# ===========================================================================
124+
# Alternative: MSAL ROPC (requires delegated user context)
125+
# ===========================================================================
126+
#
127+
# If your code needs user-specific (delegated) access rather than app-only:
128+
#
129+
# from office365.sharepoint.client_context import ClientContext
130+
# from tests import test_client_id, test_site_url, test_tenant, test_username
131+
#
132+
# ctx = ClientContext(site_url).with_username_and_password(
133+
# tenant=test_tenant,
134+
# client_id=test_client_id,
135+
# username=test_username,
136+
# password="***",
137+
# )
138+
#
139+
# Limitations of ROPC flow:
140+
# - Does not support MFA
141+
# - May be blocked by conditional access policies
142+
# - Tenant must allow public client flows in the app manifest
143+
# - Microsoft recommends certificate or interactive flows instead
144+
#
145+
# See https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc

0 commit comments

Comments
 (0)