如何使用Azure AD保护SharePoint WebPart中的重定向流程?
Great question! Exposing access tokens directly in the src attribute is a critical security risk (since it can leak via browser history, server logs, or referrer headers), so switching to an Azure AD-mediated flow is the right call. Here's a step-by-step solution using the OAuth 2.0 Authorization Code Flow with PKCE—the most secure approach for single-page app/iframe scenarios:
First, ensure your external website's Azure AD app registration is set up correctly to support this flow:
- Go to the Azure Portal → Azure AD → App Registrations → Select your external site's app.
- Under Authentication, add your callback URL (
https://www.example.org/oauthCallback.html) as a Single-page application (SPA) redirect URI. This lets Azure AD safely redirect back to your external site post-authentication. - Under API permissions, add the necessary scopes for your external site's API (e.g.,
api://your-external-api/access_as_user) and grant admin consent if required.
Instead of hardcoding a token, your WebPart will render an iframe pointing directly to Azure AD's authorization endpoint. We'll use PKCE (Proof Key for Code Exchange) to prevent authorization code interception attacks—this is non-negotiable for iframe-based flows.
Here's the modified WebPart code:
public render(): void { // Generate PKCE code verifier and challenge (critical security step) const codeVerifier = this.generateCodeVerifier(); const codeChallenge = this.generateCodeChallenge(codeVerifier); // Store verifier and state in sessionStorage (needed for callback validation) const state = this.generateRandomState(); sessionStorage.setItem('pkce_code_verifier', codeVerifier); sessionStorage.setItem('oauth_state', state); // Build Azure AD authorization URL with required parameters const authParams = new URLSearchParams({ client_id: 'YOUR_EXTERNAL_APP_CLIENT_ID', response_type: 'code', redirect_uri: 'https://www.example.org/oauthCallback.html', scope: 'openid profile api://your-external-api/access_as_user', // Adjust scopes to match your needs state: state, code_challenge: codeChallenge, code_challenge_method: 'S256', prompt: 'none' // Use silent auth: reuses existing user session if available }); const authUrl = `https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize?${authParams.toString()}`; // Render the iframe with the secure auth URL and restricted sandbox permissions this.domElement.innerHTML = ` <iframe src="${authUrl}" sandbox="allow-same-origin allow-scripts" width="100%" height="600" frameborder="0" ></iframe> `; } // Helper functions for PKCE and CSRF protection private generateCodeVerifier(): string { const array = new Uint32Array(56 / 2); window.crypto.getRandomValues(array); return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); } private generateCodeChallenge(codeVerifier: string): string { const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); return window.crypto.subtle.digest('SHA-256', data) .then(digest => btoa(String.fromCharCode(...new Uint8Array(digest)))) .then(base64 => base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')); } private generateRandomState(): string { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); }
When Azure AD authenticates the user, it will redirect the iframe to oauthCallback.html with an authorization code in the URL. Your callback page needs to:
- Validate the CSRF
stateparameter. - Exchange the authorization code for an access token using the stored PKCE code verifier.
- Use the token to load your external site's content securely.
Here's a sample implementation:
<!DOCTYPE html> <html> <body> <script> // Parse URL parameters from Azure AD redirect const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const state = urlParams.get('state'); const storedState = sessionStorage.getItem('oauth_state'); const codeVerifier = sessionStorage.getItem('pkce_code_verifier'); // Validate parameters to prevent attacks if (!code || !state || state !== storedState || !codeVerifier) { console.error('Invalid authentication parameters'); window.parent.postMessage({ status: 'error', message: 'Invalid auth params' }, '*'); return; } // Exchange authorization code for access token fetch('https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: 'YOUR_EXTERNAL_APP_CLIENT_ID', grant_type: 'authorization_code', code: code, redirect_uri: 'https://www.example.org/oauthCallback.html', code_verifier: codeVerifier }) }) .then(response => response.json()) .then(data => { if (data.access_token) { // Securely pass the token to your app (e.g., via postMessage or in-memory storage) console.log('Access token obtained safely'); // Load your external site's main content window.location.href = 'https://www.example.org/your-app-dashboard.html'; } else { console.error('Token exchange failed:', data.error); window.parent.postMessage({ status: 'error', message: data.error_description }, '*'); } }) .catch(error => { console.error('Error during token exchange:', error); window.parent.postMessage({ status: 'error', message: 'Token exchange failed' }, '*'); }); </script> </body> </html>
- Lock Down Redirect URIs: Only allow your trusted callback URL in the Azure AD app registration to prevent token leakage to malicious sites.
- Restrict Iframe Sandbox: Use the
sandboxattribute to limit the iframe's permissions (avoidallow-top-navigationunless absolutely necessary). - Token Storage: Store access tokens in
sessionStorageor memory—never expose them in URLs, cookies, or logs. - Short-Lived Tokens: Use short-lived access tokens and refresh tokens (if applicable) to minimize damage if a token is ever compromised.
- Validate All Inputs: Always validate the
stateparameter and token responses to block CSRF and tampering attacks.
内容的提问来源于stack exchange,提问作者vcima

