You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Angular和Node.js中基于Microsoft Graph API实现授权码授予流程

实现Angular + Node.js Express集成Microsoft Graph API的授权码流程

前置准备

在Azure AD注册Web应用:

  • 配置重定向URI为前端回调地址(如http://localhost:4200/auth-callback)
  • 获取客户端ID和客户端密钥
  • 添加Calendars.Read和offline_access权限(offline_access用于获取刷新令牌,实现令牌续期)
  • 确保应用启用了授权码授予流程

1. Angular前端:触发授权并获取授权码

生成授权跳转URL

创建auth.service.ts封装授权逻辑:

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly clientId = 'YOUR_AZURE_CLIENT_ID';
  private readonly redirectUri = 'http://localhost:4200/auth-callback';
  private readonly scopes = 'Calendars.Read offline_access';

  getAuthUrl(): string {
    const authEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    const params = new URLSearchParams({
      client_id: this.clientId,
      response_type: 'code',
      redirect_uri: this.redirectUri,
      scope: this.scopes,
      response_mode: 'query',
      prompt: 'select_account'
    });
    return `${authEndpoint}?${params.toString()}`;
  }
}

登录组件触发授权

在自定义登录组件中添加跳转逻辑:

import { Component } from '@angular/core';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-login',
  template: `<button (click)="initAuthFlow()">登录并授权日历访问</button>`
})
export class LoginComponent {
  constructor(private authService: AuthService) {}

  initAuthFlow(): void {
    window.location.href = this.authService.getAuthUrl();
  }
}

回调组件处理授权码

创建auth-callback组件,配置路由path: 'auth-callback',接收授权码并发送到后端:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-auth-callback',
  template: `<p>正在处理授权...</p>`
})
export class AuthCallbackComponent implements OnInit {
  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private http: HttpClient
  ) {}

  ngOnInit(): void {
    this.route.queryParams.subscribe(params => {
      const code = params['code'];
      if (!code) {
        this.router.navigate(['/login']);
        return;
      }

      this.http.post('/api/auth/exchange-code', { code }).subscribe({
        next: () => this.router.navigate(['/calendar']),
        error: () => this.router.navigate(['/login'])
      });
    });
  }
}

2. Node.js Express后端:交换令牌并存储

依赖与配置

安装依赖:

npm install axios dotenv mongoose # 根据你的数据库替换mongoose

创建.env文件存储敏感信息:

AZURE_CLIENT_ID=你的客户端ID
AZURE_CLIENT_SECRET=你的客户端密钥
AZURE_REDIRECT_URI=http://localhost:4200/auth-callback

令牌交换路由

创建routes/auth.js:

require('dotenv').config();
const express = require('express');
const axios = require('axios');
const router = express.Router();
const UserToken = require('../models/userToken'); // 数据库模型,需自行定义

// 交换授权码为访问令牌
router.post('/exchange-code', async (req, res) => {
  const { code } = req.body;
  const tokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';

  try {
    const response = await axios.post(tokenEndpoint, new URLSearchParams({
      client_id: process.env.AZURE_CLIENT_ID,
      client_secret: process.env.AZURE_CLIENT_SECRET,
      code,
      redirect_uri: process.env.AZURE_REDIRECT_URI,
      grant_type: 'authorization_code'
    }), {
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    });

    const { access_token, refresh_token, expires_in, user_id } = response.data;

    // 存储令牌到数据库,关联微软user_id或你的应用用户ID
    await UserToken.findOneAndUpdate(
      { microsoftUserId: user_id },
      {
        accessToken: access_token,
        refreshToken: refresh_token,
        expiresAt: Date.now() + expires_in * 1000
      },
      { upsert: true, new: true }
    );

    res.sendStatus(200);
  } catch (err) {
    console.error('令牌交换失败:', err.response?.data || err.message);
    res.sendStatus(500);
  }
});

module.exports = router;

数据库模型示例(MongoDB)

创建models/userToken.js:

const mongoose = require('mongoose');

const userTokenSchema = new mongoose.Schema({
  microsoftUserId: { type: String, unique: true, required: true },
  accessToken: { type: String, required: true },
  refreshToken: { type: String, required: true },
  expiresAt: { type: Date, required: true }
});

module.exports = mongoose.model('UserToken', userTokenSchema);

3. 调用Microsoft Graph API获取日历数据

在后端添加日历数据接口,自动处理令牌续期:

// 在routes/auth.js或单独的routes/calendar.js中添加
router.get('/calendar/events', async (req, res) => {
  // 从请求中获取用户标识(如JWT解析出的ID或微软user_id)
  const microsoftUserId = req.user.microsoftId; // 需根据你的认证方式调整

  try {
    let userToken = await UserToken.findOne({ microsoftUserId });
    if (!userToken) return res.sendStatus(401);

    // 令牌过期则用刷新令牌续期
    if (Date.now() > userToken.expiresAt.getTime()) {
      const tokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
      const refreshRes = await axios.post(tokenEndpoint, new URLSearchParams({
        client_id: process.env.AZURE_CLIENT_ID,
        client_secret: process.env.AZURE_CLIENT_SECRET,
        refresh_token: userToken.refreshToken,
        grant_type: 'refresh_token'
      }), {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
      });

      const { access_token, refresh_token, expires_in } = refreshRes.data;
      userToken = await UserToken.findOneAndUpdate(
        { microsoftUserId },
        {
          accessToken: access_token,
          refreshToken: refresh_token,
          expiresAt: Date.now() + expires_in * 1000
        },
        { new: true }
      );
    }

    // 调用Graph API获取日历事件
    const graphRes = await axios.get('https://graph.microsoft.com/v1.0/me/events', {
      headers: { Authorization: `Bearer ${userToken.accessToken}` }
    });

    res.json(graphRes.data);
  } catch (err) {
    console.error('获取日历数据失败:', err.response?.data || err.message);
    res.sendStatus(500);
  }
});

4. 安全最佳实践

  • 令牌加密存储:数据库中使用AES等算法加密令牌,避免明文存储
  • 后端接口认证:前端调用后端接口时需携带应用自身的认证凭证(如JWT),防止未授权访问
  • 权限最小化:仅申请Calendars.Read等必要权限,避免过度授权
  • 重定向URI限制:在Azure AD中严格限定重定向URI,仅允许可信域名

内容的提问来源于stack exchange,提问作者Jay Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:08:18