1

Idea is to build a Spring Boot Java app with which we can get MS Outlook personal account holder's calendar data, say upcoming events. Based on initial research I found out that MS-Graph API is the answer for the same, and I started with this tutorial as a starting-up code.

My application.yml file looks like this:

 spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            profile:
              tenant-id: common
            credential:
              client-id: <from azure portal>
              client-secret: <from azure portal>
            authorization-clients:
              graph:
                scopes:
                 - https://graph.microsoft.com/User.Read
                 - https://graph.microsoft.com/Calendars.Read
                 - https://graph.microsoft.com/Calendars.ReadWrite
                 - https://graph.microsoft.com/Analytics.Read

tenant-id is set common as I want to connect with any personal outlook account holder. On the Azure portal supported account types are set as - "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)" enter image description here

To test whether or not I could reach out to the MS-Graph Calendar API I added the below-mentioned code (coming from here):

EventCollectionPage events = graphClient.me().calendar().events()
    .buildRequest()
    .get();

I could able to connect to the Outlook calendar and fetch the events for my own account which happen to be the same account with which the Azure portal is being accessed. Thus my personal account abcXyz@outlook.com is also the admin account of the tenant in which the app is registered.

But when I am trying to connect to another personal outlook account, which is created by me and I added some events in the associated calendar for testing purposes I am failing. This new outlook personal account is surely not in the tenant in which the app is registered, I am not very sure which tenant the personal MS accounts go into, and how to add service-principals to them.

After configuring the app registration for all accounts on the azure portal and making the tenant-id as common in my application.yml file I was expecting to connect all the personal accounts whomsoever signed up for the Java app but faced the following errors. It seems as if the Microsoft identity platform is not letting this other user connect its calendar to my Java app, as the service-principals of my app are not stored in the personal account holder's tenant. But How to do that? Is my approach to problem is right or should I instead of leveraging Spring libraries (OAuth2Client, AzureActiveDirectory) prefer building OAuth2.0 client on my own, reaching to /authorize and /token endpoints on my own, though MS doesn't recommend that as mentioned here?

java.lang.IllegalArgumentException: Missing attribute 'name' in attributes
at org.springframework.security.oauth2.core.user.DefaultOAuth2User.<init>(DefaultOAuth2User.java:72) ~[spring-security-oauth2-core-5.6.7.jar:5.6.7]
at org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.<init>(DefaultOidcUser.java:93) ~[spring-security-oauth2-core-5.6.7.jar:5.6.7]
at org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.<init>(DefaultOidcUser.java:67) ~[spring-security-oauth2-core-5.6.7.jar:5.6.7]
at com.azure.spring.cloud.autoconfigure.aad.implementation.webapp.AadOAuth2UserService.loadUser(AadOAuth2UserService.java:134) ~[spring-cloud-azure-autoconfigure-4.3.0.jar:4.3.0]
at com.azure.spring.cloud.autoconfigure.aad.implementation.webapp.AadOAuth2UserService.loadUser(AadOAuth2UserService.java:52) ~[spring-cloud-azure-autoconfigure-4.3.0.jar:4.3.0]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:156) ~[spring-security-oauth2-client-5.6.7.jar:5.6.7]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.6.7.jar:5.6.7]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:195) ~[spring-security-oauth2-client-5.6.7.jar:5.6.7]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178) ~[spring-security-oauth2-client-5.6.7.jar:5.6.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.22.jar:5.3.22]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.22.jar:5.3.22]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.22.jar:5.3.22]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.22.jar:5.3.22]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.6.7.jar:5.6.7]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.6.7.jar:5.6.7]
Vyassa
  • 33
  • 7

2 Answers2

1

spring-cloud-azure-starter-active-directory is designed to support Work/School account. Whether Personal account is supported is not sure for now. It's tracked in this GitHub issue.

For the difference between Work/School account and Personal account, please refer to this page.

chenrujun
  • 126
  • 4
  • Thanks for replying Rujun! I found out MS has this concept of users being located in the tenant, and for an oauth2 app registered in a tenant to talk to users of another tenant we need the following things: 1. Admins of those tenants explicitly add the service principals (a JSON) in those tenants. 2. And I need to make my oauth2 client app a multi-tenant one. Now I don't understand who is the admin for personal accounts of MS. Please check : https://stackoverflow.com/questions/70355146/how-to-fetch-calendar-events-for-a-user-using-microsoft-graph-api-with-java – Vyassa Oct 14 '22 at 22:05
  • In the link above we can see the guy is dealing with the authorization code of authorization code grant flow of oauth2. This low-level implementation is not recommended by MS and neither the Spring tutorials are geared like this, but I couldn't find any solution to the original problem and this link (in the above comment) made me think of building an oauth2 client at a low level rather. Still looking for a proper answer, though, thanks for replying once again! – Vyassa Oct 14 '22 at 22:11
1

Use this sample to learn how to call graph

Individuals are the administrators of their personal accounts. When they go to log into your application, there will be a list of actions that they need to consent to before they before they can use your application. In your case, one of them would be something like "read calendar." The individual would then grant your application the ability to read their outlook calendar. Hope this helps Vyassa.

DidunPM
  • 11
  • 2