agentsclimarketplace

Onenote security basics

Skill jeremylongshore/claude-code-plugins-plus-skills/skills/.curated/onenote-security-basics

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill onenote-security-basics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Implement secure authentication, token management, and permission scoping for OneNote Graph API.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

10.2 KB, as published. Nobody here has run it

OneNote Security Basics

Overview

OneNote Graph API security changed fundamentally on March 31, 2025, when Microsoft deprecated app-only authentication for OneNote endpoints. Every integration must now use delegated authentication through MSAL, which means real users must sign in — no more background service accounts with client secrets. This skill covers the full security surface: permission scoping, token lifecycle management, MSAL cache serialization, credential storage, and multi-tenant hardening. Get any of these wrong and your integration either breaks silently (expired tokens returning 401s) or over-provisions access (Notes.ReadWrite.All when Notes.Read suffices).

Prerequisites

  • Azure AD app registration with redirect URI configured at https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps
  • Microsoft 365 license (E3/E5/Business) with OneNote enabled
  • Python: pip install msgraph-sdk azure-identity msal or Node: npm install @microsoft/microsoft-graph-client @azure/identity @azure/msal-node
  • Understanding of OAuth 2.0 authorization code flow and delegated permissions

Instructions

Permission Scope Matrix

Choose the minimum scope required for your use case:

ScopeRead notebooksRead pagesCreate pagesCreate notebooksAdmin consent?
Notes.ReadYesYesNoNoNo
Notes.ReadWriteYesYesYesYesNo
Notes.ReadWrite.AllYesYesYesYesYes
Notes.CreateNoNoYesYesNo

Least-privilege recommendations:

  • Read-only dashboards: Notes.Read (user consent only)
  • Personal note creation: Notes.ReadWrite (user consent only)
  • Cross-user/organizational access: Notes.ReadWrite.All (requires tenant admin approval)
  • Write-only ingestion: Notes.Create (cannot read back what was written)

Delegated Authentication Setup (Post-2025 Mandatory)

CRITICAL: App-only authentication (ClientSecretCredential) was deprecated for OneNote endpoints on March 31, 2025. All code below uses delegated auth exclusively.

Python — Device Code Flow (headless/CLI environments):

from azure.identity import DeviceCodeCredential
from msgraph import GraphServiceClient
import os

CLIENT_ID = os.environ["AZURE_CLIENT_ID"]
TENANT_ID = os.environ["AZURE_TENANT_ID"]

# Minimal scopes — only request what you need
scopes = ["Notes.ReadWrite"]

credential = DeviceCodeCredential(
    client_id=CLIENT_ID,
    tenant_id=TENANT_ID,
    # cache_persistence_options enables silent token renewal
)
client = GraphServiceClient(credentials=credential, scopes=scopes)

TypeScript — Interactive Browser Flow (web apps):

import { DeviceCodeCredential } from "@azure/identity";
import { Client } from "@microsoft/microsoft-graph-client";
import { TokenCredentialAuthenticationProvider }
  from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";

const credential = new DeviceCodeCredential({
  clientId: process.env.AZURE_CLIENT_ID!,
  tenantId: process.env.AZURE_TENANT_ID!,
});

const scopes = ["Notes.ReadWrite"];
const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes });
const client = Client.initWithMiddleware({ authProvider });

Token Lifecycle Management

Access tokens expire after 1 hour. Refresh tokens last 90 days but can be revoked by admin policy. Your code must handle silent renewal:

# Python: MSAL token cache serialization for persistent sessions
import msal
import json
import os

CACHE_FILE = os.path.expanduser("~/.onenote-token-cache.json")

def get_msal_app():
    cache = msal.SerializableTokenCache()
    if os.path.exists(CACHE_FILE):
        cache.deserialize(open(CACHE_FILE).read())

    app = msal.PublicClientApplication(
        client_id=os.environ["AZURE_CLIENT_ID"],
        authority=f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}",
        token_cache=cache,
    )
    return app, cache

def acquire_token(app, cache):
    accounts = app.get_accounts()
    if accounts:
        # Silent renewal — no user interaction needed if refresh token valid
        result = app.acquire_token_silent(
            scopes=["https://graph.microsoft.com/Notes.ReadWrite"],
            account=accounts[0],
        )
        if result and "access_token" in result:
            save_cache(cache)
            return result["access_token"]

    # Fallback: device code flow requires user interaction
    flow = app.initiate_device_flow(
        scopes=["https://graph.microsoft.com/Notes.ReadWrite"]
    )
    print(flow["message"])  # "Go to https://microsoft.com/devicelogin..."
    result = app.acquire_token_by_device_flow(flow)
    save_cache(cache)
    return result.get("access_token")

def save_cache(cache):
    if cache.has_state_changed:
        with open(CACHE_FILE, "w") as f:
            f.write(cache.serialize())
        os.chmod(CACHE_FILE, 0o600)  # Owner-only read/write

Secure Credential Storage

Never store client IDs or tenant IDs in source code. Use environment variables at minimum, Azure Key Vault for production:

# Development: .env file (add to .gitignore FIRST)
echo ".env" >> .gitignore
cat > .env << 'EOF'
AZURE_CLIENT_ID=your-app-registration-client-id
AZURE_TENANT_ID=your-directory-tenant-id
EOF
chmod 600 .env
# Production: Azure Key Vault integration
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

vault_url = "https://your-vault.vault.azure.net"
kv_client = SecretClient(vault_url=vault_url, credential=DefaultAzureCredential())

client_id = kv_client.get_secret("onenote-client-id").value
tenant_id = kv_client.get_secret("onenote-tenant-id").value

Multi-Tenant Security Considerations

For apps serving multiple organizations:

  • Register as a multi-tenant app (set supportedAccountTypes to AzureADMultipleOrgs)
  • Validate the tid (tenant ID) claim in every token — reject tokens from unexpected tenants
  • Store per-tenant token caches separately (never mix tenant tokens)
  • Handle Conditional Access policies: catch claims challenge in 401 responses and re-authenticate with the required claims

Security Checklist for Production

  • Using delegated auth (NOT app-only/ClientSecretCredential — deprecated March 2025)
  • Minimum required scopes (Notes.Read unless writes needed)
  • Token cache file has 0600 permissions (owner-only)
  • MSAL cache serialized to disk for silent renewal
  • Client ID and tenant ID sourced from environment or Key Vault
  • .env file in .gitignore
  • Token claims validated (aud, tid, exp)
  • Refresh token rotation monitored (90-day expiry alert)
  • Admin consent obtained for Notes.ReadWrite.All (if needed)
  • Conditional Access error handling implemented

Output

After applying this skill, your OneNote integration will have: least-privilege permission scoping matched to actual usage, persistent MSAL token cache with silent renewal, secure credential storage using environment variables or Key Vault, and a verified security checklist. Authentication failures will produce actionable error messages instead of silent 401 loops.

Error Handling

ErrorCauseFix
AADSTS65001: user needs to consentScope not yet granted by userRedirect to consent URL or use admin consent endpoint
AADSTS700016: app not foundWrong client ID or wrong tenantVerify AZURE_CLIENT_ID matches portal registration
AADSTS50076: MFA requiredConditional Access policyUse InteractiveBrowserCredential (device code cannot handle MFA prompts)
403 Forbidden on OneNote callsMissing Notes.* permission or using app-only authCheck scope in token; switch to delegated auth
401 Unauthorized after workingAccess token expired, silent renewal failedCheck refresh token validity; re-serialize cache
Token cache file empty after restartCache not serialized on shutdownCall save_cache() in atexit handler

Examples

Verify your current token scopes:

import requests

def check_token_scopes(access_token: str) -> list[str]:
    """Decode token to inspect granted scopes (without validation)."""
    import base64, json
    payload = access_token.split(".")[1]
    payload += "=" * (4 - len(payload) % 4)  # pad base64
    claims = json.loads(base64.urlsafe_b64decode(payload))
    return claims.get("scp", "").split(" ")

# Usage
scopes = check_token_scopes(token)
if "Notes.ReadWrite" not in scopes:
    raise PermissionError(f"Token only has: {scopes}. Need Notes.ReadWrite.")

Rotate to new credentials without downtime:

# 1. Register new app in Azure portal
# 2. Update Key Vault with new credentials
az keyvault secret set --vault-name your-vault --name onenote-client-id --value NEW_CLIENT_ID
# 3. Clear MSAL cache to force re-auth with new app
rm ~/.onenote-token-cache.json
# 4. First request will trigger device code flow with new app

Resources

Next Steps

  • Apply onenote-prod-checklist for full production readiness review
  • Use onenote-reference-architecture to understand API path differences across notebook locations
  • See onenote-rate-limits for throttling and Retry-After handling

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.