Azure AD B2C 未获取访问令牌

Azure AD B2C Not getting Access Token

我有一个用 ReactJS 编写的单页应用程序 (SPA)。我正在尝试查询图表 API。我正在使用 msal.js 库来处理我的身份验证。我正在使用 Azure AD B2C 来管理我的帐户。我目前拥有的:

我可以登录 Google 并将我的个人资料信息返回到我的 SPA。但是,当请求 accessToken 时,结果为空。我很肯定我做错了什么,我猜这与我的应用程序注册和范围有关。当我注册我的应用程序时,它有一个名为 "user_impersonation" 的默认范围。当我输入这个时,我意识到我注册了我的客户端 (SPA) 而不是 API。我需要同时注册吗?我不确定如何注册 Graph。

我的代码:

App.js

import AuthService from '../services/auth.service';
import GraphService from '../services/graph.service';

class App extends Component {
  constructor() {
  super();
  this.authService = new AuthService();
  this.graphService = new GraphService();
  this.state = {
    user: null,
    userInfo: null,
    apiCallFailed: false,
    loginFailed: false
  };
}
  componentWillMount() {}

  callAPI = () => {
    this.setState({
    apiCallFailed: false
  });
  this.authService.getToken().then(
    token => {
      this.graphService.getUserInfo(token).then(
        data => {
          this.setState({
            userInfo: data
          });
        },
        error => {
          console.error(error);
          this.setState({
            apiCallFailed: true
          });
        }
      );
    },
    error => {
      console.error(error);
      this.setState({
        apiCallFailed: true
      });
    }
  );
};

logout = () => {
  this.authService.logout();
};

login = () => {
  this.setState({
    loginFailed: false
  });
  this.authService.login().then(
    user => {
      if (user) {
        this.setState({
          user: user
        });
      } else {
        this.setState({
          loginFailed: true
        });
      }
    },
    () => {
      this.setState({
       loginFailed: true
      });
    }
  );
};

render() {
  let templates = [];
  if (this.state.user) {
    templates.push(
      <div key="loggedIn">
        <button onClick={this.callAPI} type="button">
          Call Graph's /me API
        </button>
        <button onClick={this.logout} type="button">
          Logout
        </button>
        <h3>Hello {this.state.user.name}</h3>
      </div>
    );
  } else {
    templates.push(
      <div key="loggedIn">
        <button onClick={this.login} type="button">
          Login with Google
        </button>
      </div>
    );
  }
  if (this.state.userInfo) {
    templates.push(
      <pre key="userInfo">{JSON.stringify(this.state.userInfo, null, 4)}</pre>
    );
  }
  if (this.state.loginFailed) {
    templates.push(<strong key="loginFailed">Login unsuccessful</strong>);
  }
  if (this.state.apiCallFailed) {
    templates.push(
      <strong key="apiCallFailed">Graph API call unsuccessful</strong>
    );
  }
  return (
    <div className="App">
      <Header />
      <Main />
        {templates}
    </div>
  );
 }
}

export default App

auth.service.js:

import * as Msal from 'msal';

export default class AuthService {
constructor() {
// let PROD_REDIRECT_URI = 'https://sunilbandla.github.io/react-msal-sample/';
// let redirectUri = window.location.origin;
// let redirectUri = 'http://localhost:3000/auth/openid/return'
// if (window.location.hostname !== '127.0.0.1') {
//   redirectUri = PROD_REDIRECT_URI;
// }
this.applicationConfig = {
  clientID: 'my_client_id',
  authority: "https://login.microsoftonline.com/tfp/my_app_name.onmicrosoft.com/b2c_1_google-sisu",
  b2cScopes: ['https://my_app_name.onmicrosoft.com/my_api_name/user_impersonation email openid profile']
  // b2cScopes: ['graph.microsoft.com user.read']
};
this.app = new Msal.UserAgentApplication(this.applicationConfig.clientID, this.applicationConfig.authority, function (errorDesc, token, error, tokenType) {});
// this.logger = new Msal.logger(loggerCallback, { level: Msal.LogLevel.Verbose, correlationId:'12345' });
}


login = () => {
    return this.app.loginPopup(this.applicationConfig.b2cScopes).then(
    idToken => {
    const user = this.app.getUser();
    if (user) {
      return user;
    } else {
      return null;
    }
  },
   () => {
      return null;
    }
  );
};


logout = () => {
  this.app.logout();
};


getToken = () => {
  return this.app.acquireTokenSilent(this.applicationConfig.b2cScopes).then(
    accessToken => {
      return accessToken;
    },
    error => {
      return this.app
        .acquireTokenPopup(this.applicationConfig.b2cScopes)
        .then(
          accessToken => {
            return accessToken;
          },
          err => {
            console.error(err);
          }
        );
      }
    );
  };
}

graph.service.js

export default class GraphService {
constructor() {
  // this.graphUrl = 'https://graph.microsoft.com/v1.0';
  this.graphUrl = 'http://httpbin.org/headers';
}

getUserInfo = token => {
  const headers = new Headers({ Authorization: `Bearer ${token}` });
  const options = {
    headers
  };
  return fetch(`${this.graphUrl}`, options)
    .then(response => response.json())
    .catch(response => {
      throw new Error(response.text());
    });
  };
}

我在尝试查询图 API 时收到拒绝访问错误,所以我用 httpbin 替换了 graph.microsoft.com/1.0 这样我就可以真正看到传递的内容in. 这是我看到令牌为空的地方。这是我从 Microsoft 的示例项目中提取的完全相同的代码,当我使用他们的代码时它可以工作。他们没有显示的是 AAD B2C 和应用程序注册的配置方式,我认为这是我的问题所在。

抱歉,这可能会让用户有点困惑,但这里有几种不同的服务。

Azure AD Graph API - it is same for B2C

Microsoft Graph

然后,在 Azure AD B2C 本身中,您有 B2C 部分的 Application Registration。使用此应用程序注册,您不能使用任何图表。

还有一个正常的Application Integration in Azure Active Directory which is valid for the Graphs. However this type of application integrations (also as described here) 不能用于B2C流程

所以,再说一遍:

You cannot use any of the Graph APIs (neither Azure AD Graph API nor Microsoft Graph) from the context of an B2C application.

AND

You cannot use any B2C functionality (like login with google, etc.) from the context of "normal" app registered for use with Graph.

因此,在 B2C registered application you can only request and get an access_token for an API which is also registered for use with B2C 的上下文中。

我的问题是我需要注册 2 个应用程序:1 个用于我的客户,1 个用于 API。然后,在 Azure AD B2C 门户中,我必须授予客户端对 API 的访问权限。一旦我这样做了,就会得到一个访问令牌。