Skip to main content

Microsoft

Microsoft services such as SharePoint, OneDrive, and other resources exposed through the Microsoft Graph API can be integrated with VIKTOR using the OAuth 2.0 workflow for token based authentication and authorization. Authentication is handled by Microsoft Entra ID (formerly Azure Active Directory). A general introduction to OAuth 2.0 can be found here.

This guide uses SharePoint as the running example, but the same setup applies to any Microsoft Graph resource. You only need to adjust the scopes and the endpoints your app calls.

Prerequisites

To create this integration you need:

  • A Microsoft Entra ID tenant and an account that is allowed to register applications in the Microsoft Entra admin center (or the Azure portal). Registering apps or granting admin consent for tenant-wide permissions typically requires an administrator.
  • Access to the SharePoint sites (or other Microsoft resources) you want to read from, with the account that will log in to the app.

This tutorial registers a Microsoft Web application because VIKTOR's OAuth2Integration expects a confidential client with a client_id, client_secret, and a redirect callback.

Registering an application in Microsoft Entra

Open the Microsoft Entra admin center and go to Identity > Applications > App registrations > New registration. This application acts as the bridge between VIKTOR and your Microsoft tenant, allowing secure data exchange through OAuth 2.0.

Register an application in Microsoft Entra

Configure the registration as follows:

  1. Name: choose a descriptive name, for example viktor-sharepoint-integration.

  2. Supported account types: select Accounts in this organizational directory only for a single tenant. Choose a multi-tenant option only if you explicitly need to support external tenants.

  3. Redirect URI: set the platform to Web and add your VIKTOR callback URL:

    <your-viktor-environment-url>/api/integrations/oauth2/callback/

    Your environment URL should be https://cloud.viktor.ai/ or similar, depending on your organization. For example, if your VIKTOR environment is hosted at demo.viktor.ai, your redirect URI should be:

    https://demo.viktor.ai/api/integrations/oauth2/callback/
    Note

    The redirect URI must match your VIKTOR environment URL exactly, otherwise the integration will fail.

After registering, note the following values from the application Overview page:

  • Application (client) ID
  • Directory (tenant) ID (used to build the authentication and token URLs)

Adding API permissions

Go to API permissions > Add a permission > Microsoft Graph > Delegated permissions, and add the permissions your app needs. For browsing SharePoint sites and reading files, add:

  • User.Read
  • Sites.Read.All
  • Files.Read.All

Add offline_access as well if you want refresh-token support so the user does not have to log in again as often.

Microsoft Graph delegated permissions

Which permissions you need depends on what the app does:

What the app doesDelegated permissions to add
Sign in and read the user profileUser.Read
Browse SharePoint sites and read filesSites.Read.All, Files.Read.All
Upload, replace, or delete SharePoint filesSites.ReadWrite.All, Files.ReadWrite.All
Send email as the logged-in userMail.Send

The write permissions include the read permissions, so you do not need to add both Files.Read.All and Files.ReadWrite.All.

Security best practices

Use the minimum permissions required for your application to function. For example, if your app only reads data, do not request write permissions. This limits the impact in case of misuse.

Some permissions require admin consent. If the Status column shows that consent is required, an administrator must click Grant admin consent for <tenant>.

Creating a client secret

Go to Certificates & secrets > Client secrets > New client secret. Add a description and an expiration period, then create the secret.

Create a client secret (step 1)

Create a client secret (step 2)

Note

Copy the client secret Value (not the Secret ID) immediately. You will not be able to view it again after leaving the page.

Security best practices

Use a short expiration for your client secret and rotate it regularly. Use a short Time-to-Live for your access tokens as well.

After completing this section you should have the following values ready:

  • Application (client) ID
  • Client secret value
  • Directory (tenant) ID

Creating an OAuth 2.0 integration (admin)

After registering the application, a VIKTOR admin can generate an OAuth 2.0 integration.

Note

VIKTOR does not have a dedicated Microsoft integration yet, so you set up the connection using the Generic OAuth 2.0 integration. The steps below are identical, you only fill in Microsoft's endpoints, credentials, and scopes.

Start in the Administrator panel and create a new OAuth 2.0 integration with Generic as the selected software.

Open the OAuth 2.0 modal

  1. Navigate to the Integrations tab in the Administrator panel.

  2. Select the OAuth 2.0 tab.

  3. Follow the steps in the modal:

    • Select Generic.
    • Go to the Basic Information tab and fill in the required fields, including a descriptive integration name, and select the applications that will use the integration. You can choose to limit the integration to specific apps or allow all apps in the environment to use it.
    note

    Throughout this guide we use microsoft-entra as the example integration name. When you see this name in code samples or configuration steps, replace it with your own chosen name. This name is referenced in your application code, so make it memorable and meaningful.

    • Then, in the Configuration tab, add the Authentication URL and Token URL. Replace <tenant-id> with your Directory (tenant) ID from the previous section:
      • Authentication URL: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
      • Token URL: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
    Note

    Make sure to use the v2.0 endpoints as shown above. The v2.0 endpoints are the ones compatible with the scope-based OAuth 2.0 configuration used by VIKTOR.

  4. Fill in the Client ID (Application ID) and Client Secret (the secret value) from your Microsoft Entra application.

  5. For the scopes, use the same delegated permissions you configured in the app registration, separated by spaces:

    offline_access User.Read Sites.Read.All Files.Read.All

    If the app also writes files or sends email, extend the list with the matching scopes, for example:

    offline_access User.Read Sites.ReadWrite.All Files.ReadWrite.All Mail.Send
    note

    If your tenant requires it, you can prefix the Microsoft Graph scopes with the resource URL, for example https://graph.microsoft.com/Sites.Read.All. The short form shown above works for the v2.0 endpoint in most tenants.

VIKTOR Microsoft OAuth configuration

Implementing the integration

Once an administrator sets up the OAuth 2.0 integration, the developer can start the implementation. You have two alternatives to develop apps that integrate Microsoft services:

Using the App Builder

The App Builder provides a no-code approach to create applications with a Microsoft integration. Simply provide a natural language prompt describing what you want to build, and the App Builder will generate the application for you.

To create an app that lists the SharePoint sites you have access to, you can run the following prompt (make sure to replace microsoft-entra with the name of the integration you created in the previous steps):

Create an app using my OAuth2 integration 'microsoft-entra' to fetch SharePoint sites from:
URL: https://graph.microsoft.com/v1.0/sites?search=*

The API returns JSON with a 'value' list, where each site has 'displayName' and 'webUrl' fields. Display these in a table.

After submitting the prompt, the App Builder will ask you to set up the integration by following these 3 steps:

  1. Click the App details button
  2. Select the integration name microsoft-entra
  3. Save the current selection

Using local development

For local development, the developer should add the integration name to the app configuration (after the administrator has assigned the integration to the app). The platform uses it to show a login button when the user opens the app. Make sure your viktor.config.toml contains the name exactly as configured in the Administrator panel.

note

Remember to use your own integration name in viktor.config.toml. In the examples below we continue using microsoft-entra, but you should replace this with the custom name you defined earlier in the Administrator panel.

viktor.config.toml:

app_type = "editor"
python_version = "3.13"
registered_name = "your-app-name"
oauth2_integrations = [
"microsoft-entra"
]

Then implement the logic that uses the integration in the code. You can use raw REST requests or a Python SDK, as long as it is compatible with the VIKTOR OAuth flow. A popular library is requests. You can add it in your requirements.txt:

viktor
requests

To obtain an access token, instantiate OAuth2Integration with the integration name and call get_access_token. All Microsoft Graph requests use the token as a bearer token in the Authorization header. The example below fetches the SharePoint sites the user has access to and displays them in a TableView. It works when the VIKTOR OAuth 2.0 integration is assigned to your app and the user has granted access:

import viktor as vkt
import requests

class Parametrization(vkt.Parametrization):
text = vkt.Text("# Microsoft SharePoint OAuth2 Integration")

class Controller(vkt.Controller):
parametrization = Parametrization

@vkt.TableView("SharePoint sites")
def get_sites(self, params, **kwargs):
integration = vkt.external.OAuth2Integration("microsoft-entra")
token = integration.get_access_token()

headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json",
}
endpoint = "https://graph.microsoft.com/v1.0/sites?search=*"
response = requests.get(endpoint, headers=headers, timeout=15)
response.raise_for_status()

sites = response.json().get("value", [])

data = []
for site in sites:
site_name = site.get("displayName", "")
site_url = site.get("webUrl", "")
data.append([site_name, site_url])

return vkt.TableResult(data, column_headers=["Site name", "URL"])
Note

The integration name in OAuth2Integration must match the value in viktor.config.toml. Make sure the user has access to the relevant SharePoint sites (or other Microsoft resources) in the connected account.

Reading and modifying SharePoint files

SharePoint files are reached through the Microsoft Graph drive endpoints. A site contains one or more document libraries (drives), and each drive contains folders and files (drive items). Reading and writing a file therefore takes two steps: look up the drive of the site, then address the file inside that drive.

The examples below reuse the same headers as in the previous section:

integration = vkt.external.OAuth2Integration("microsoft-entra")
token = integration.get_access_token()
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}

Looking up a site and its document library

You can address a site by its hostname and path, which avoids hardcoding an internal site ID:

BASE = "https://graph.microsoft.com/v1.0"

# https://contoso.sharepoint.com/sites/engineering
site = requests.get(f"{BASE}/sites/viktor.sharepoint.com:/sites/engineering", headers=headers, timeout=15)
site.raise_for_status()
site_id = site.json()["id"]

# the default document library ("Documents") of the site
drive = requests.get(f"{BASE}/sites/{site_id}/drive", headers=headers, timeout=15)
drive.raise_for_status()
drive_id = drive.json()["id"]

Use /sites/{site-id}/drives instead of /sites/{site-id}/drive to list all document libraries of a site, and /drives/{drive-id}/root/children to list the items in the root folder of a library.

Reading a file

Files are read with the content endpoint, either by item ID or by path. The example below downloads a file by its path in the document library and hands it to the user with a DownloadButton:

import viktor as vkt
import requests

BASE = "https://graph.microsoft.com/v1.0"
DRIVE_ID = "..." # see "Looking up a site and its document library"
FILE_PATH = "Projects/beam-loads.xlsx"

class Parametrization(vkt.Parametrization):
download = vkt.DownloadButton("Download from SharePoint", method="download_file")

class Controller(vkt.Controller):
parametrization = Parametrization

def download_file(self, params, **kwargs):
integration = vkt.external.OAuth2Integration("microsoft-entra")
token = integration.get_access_token()
headers = {"Authorization": f"Bearer {token}"}

response = requests.get(
f"{BASE}/drives/{DRIVE_ID}/root:/{FILE_PATH}:/content",
headers=headers,
timeout=60,
)
response.raise_for_status()

return vkt.DownloadResult(vkt.File.from_data(response.content), "beam-loads.xlsx")

The response body is the raw file content, so you can also feed it straight into your own processing logic, for example openpyxl for a spreadsheet or vkt.File.from_data(response.content) for any VIKTOR function that accepts a File.

Modifying a file

Writing a file uses the same path with a PUT request. Uploading to a path that already exists replaces the file and creates a new version in SharePoint; uploading to a path that does not exist creates the file. This requires the Files.ReadWrite.All and Sites.ReadWrite.All scopes.

Do this in an action rather than in a view, because views are re-evaluated whenever the user changes an input and would write to SharePoint every time:

import viktor as vkt
import requests

BASE = "https://graph.microsoft.com/v1.0"
DRIVE_ID = "..."

class Parametrization(vkt.Parametrization):
upload_file = vkt.FileField("File to upload", file_types=[".xlsx"])
upload = vkt.ActionButton("Upload to SharePoint", method="upload_to_sharepoint")

class Controller(vkt.Controller):
parametrization = Parametrization

def upload_to_sharepoint(self, params, **kwargs):
if not params.upload_file:
raise vkt.UserError("Select a file first.")

integration = vkt.external.OAuth2Integration("microsoft-entra")
token = integration.get_access_token()

content = params.upload_file.file.getvalue_binary()
target = f"Projects/{params.upload_file.filename}"

response = requests.put(
f"{BASE}/drives/{DRIVE_ID}/root:/{target}:/content",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/octet-stream",
},
data=content,
timeout=60,
)
response.raise_for_status()

vkt.UserMessage.success(f"Uploaded {params.upload_file.filename} to SharePoint.")
Note

A simple PUT upload is limited to 4 MB. For larger files, create an upload session with POST /drives/{drive-id}/root:/{path}:/createUploadSession and send the file in chunks. See upload large files in the Microsoft Graph documentation.

Other common operations on drive items follow the same pattern: PATCH /drives/{drive-id}/items/{item-id} renames or moves an item, DELETE /drives/{drive-id}/items/{item-id} removes it, and POST /drives/{drive-id}/items/{parent-item-id}/children creates a folder. See the Microsoft Graph SharePoint documentation for the full list of endpoints.

Sending email from an app

Email is sent through the Microsoft Graph sendMail endpoint. Because the integration uses delegated permissions, the message is sent from the mailbox of the user who is logged in to the app, and it appears in their Sent Items. Add the Mail.Send scope to both the app registration and the VIKTOR integration to use this.

import viktor as vkt
import requests

class Parametrization(vkt.Parametrization):
recipient = vkt.TextField("Send results to")
send = vkt.ActionButton("Send email", method="send_email")

class Controller(vkt.Controller):
parametrization = Parametrization

def send_email(self, params, **kwargs):
integration = vkt.external.OAuth2Integration("microsoft-entra")
token = integration.get_access_token()

message = {
"message": {
"subject": "Calculation results",
"body": {
"contentType": "HTML",
"content": "<p>The calculation finished. The results are attached.</p>",
},
"toRecipients": [{"emailAddress": {"address": params.recipient}}],
},
"saveToSentItems": True,
}

response = requests.post(
"https://graph.microsoft.com/v1.0/me/sendMail",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json=message,
timeout=30,
)
response.raise_for_status()

vkt.UserMessage.success(f"Email sent to {params.recipient}.")

A successful call returns status 202 Accepted with an empty body, which means Microsoft accepted the message for delivery.

Adding an attachment

Attachments are sent inline as base64-encoded content in the same request. The example below attaches a PDF report generated by the app:

import base64

report = ... # a vkt.File, for example the result of a report generation
encoded = base64.b64encode(report.getvalue_binary()).decode()

message = {
"message": {
"subject": "Calculation results",
"body": {"contentType": "Text", "content": "The report is attached."},
"toRecipients": [{"emailAddress": {"address": params.recipient}}],
"attachments": [
{
"@odata.type": "#microsoft.graph.fileAttachment",
"name": "report.pdf",
"contentType": "application/pdf",
"contentBytes": encoded,
}
],
},
"saveToSentItems": True,
}
Note

The total size of a message including its attachments is limited to 4 MB on the sendMail endpoint. For larger attachments, create a draft message with POST /me/messages, add the attachment with an upload session, and send the draft with POST /me/messages/{id}/send.

FAQs

How do I read or modify SharePoint files?

Address the file through the Microsoft Graph drive endpoints: look up the document library of the site, then read the file with GET /drives/{drive-id}/root:/{path}:/content or write it with a PUT to the same path. Reading needs the Files.Read.All and Sites.Read.All scopes, writing needs Files.ReadWrite.All and Sites.ReadWrite.All. See Reading and modifying SharePoint files for full examples.

How do I send emails from an app?

Add the Mail.Send scope to the app registration and the VIKTOR integration, then post the message to https://graph.microsoft.com/v1.0/me/sendMail with the access token of the integration. The email is sent from the mailbox of the user who is logged in to the app. See Sending email from an app for a working example, including attachments.

Can the app act on its own, without a user logging in?

No. This integration uses the OAuth 2.0 authorization code flow with delegated permissions, so every call is made on behalf of the logged-in user and is limited to what that user may see and do. A background job or a shared service account is not supported by this integration.

I get a 403 Forbidden from Microsoft Graph

The token was accepted but the requested scope is missing or was never consented to. Check that the permission is listed in API permissions of the app registration, that its Status shows granted consent, and that the same scope is present in the scopes field of the VIKTOR integration. After changing the scopes, the user has to log in again so a new token is issued.

I get a 404 Not Found for a site or file that exists

Microsoft Graph returns 404 for items the logged-in user cannot access, so this is often a permission issue on the SharePoint site rather than a wrong path. Confirm that the account used to log in to the app has access to the site, and check the path: paths in root:/{path}: are relative to the root of the document library, not to the site URL.