DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Knowledge Graph Embeddings and NLP Innovations
  • Blue Skies Ahead: An AI Case Study on LLM Use for a Graph Theory Related Application
  • Graph API for Entra ID (Azure AD) Object Management
  • Building Neural Networks With Automatic Differentiation

Trending

  • Enforcing Architecture With ArchUnit in Java
  • How to Ensure Cross-Time Zone Data Integrity and Consistency in Global Data Pipelines
  • Accelerating Debugging in Integration Testing: An Efficient Search-Based Workflow for Impact Localization
  • Detection and Mitigation of Lateral Movement in Cloud Networks

Commonly Occurring Errors in Microsoft Graph Integrations and How to Troubleshoot Them (Part 1)

This article documents common integration errors that may be seen for Microsoft Graph integrations into business apps and ways to handle those errors.

By 
Constantin Kwiatkowski user avatar
Constantin Kwiatkowski
·
Aug. 29, 22 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
5.9K Views

Join the DZone community and get the full member experience.

Join For Free

With the release of Exchange Server in 2007, Microsoft also introduced Exchange Web Services (EWS). These SOAP-based APIs allow developers to access Microsoft Exchange products such as calendars, contacts, etc. over the internet. As part of the Office 365 product launch, Microsoft also released the new REST-based Office 365 Unified APIs, later known as the MS Graph APIs, in 2015. Microsoft announced in 2018 that there would be no more active development for the EWS APIs. The Exchange Web Services no longer meet today's security and maintenance requirements. For this reason, the various EWS APIs will be gradually shut down from 2022 on and replaced by the MS Graph APIs. An overview of services already accessible via the MS Graph APIs can be found in the current documentation from Microsoft. Due to the switch to MS Graph, various companies have to adapt their digital products. 

In this article series, we will discuss some of the lessons learned during such a transition to Microsoft Graph REST API v1.0 with the MS Graph Java SDK.

Attach Large Files to Outlook Events (Audience Claim Value Is Invalid)

According to Microsoft documentation, only attachments up to 150MB in size can be uploaded and attached for events. Attachments up to 3MB in size can be uploaded with a single POST call. For attachments between 3MB and 150 MB, an upload session is generated. Within the session, the attachment is uploaded piece by piece via multiple PUT calls. For the second case, consider the following example:

Java
 
final String primaryUser = "office365userEmail"; 
final String eventID = "eventID";

final GraphServiceClient<Request> graphClient = GraphServiceClient
                        .builder()
                        .authenticationProvider(getAuthenticationProvider())
                        .buildClient();

final File file = File.createTempFile("testFile", "txt"); 
FileUtils.writeByteArrayToFile(file, "testContent".getBytes());
InputStream fileStream = new FileInputStream(file);

final AttachmentItem attachmentItem = new AttachmentItem();
attachmentItem.attachmentType = AttachmentType.FILE;
attachmentItem.name = file.getName();
attachmentItem.size = file.getTotalSpace();

final AttachmentCreateUploadSessionParameterSet attachmentCreateUploadSessionParameterSet = AttachmentCreateUploadSessionParameterSet
						.newBuilder()
                        .withAttachmentItem(attachmentItem)
                        .build();

final UploadSession uploadSession = graphClient
	                    .users(primaryUser)
	                    .events(evendID)   
		                .attachments()
	                    .createUploadSession(attachmentCreateUploadSessionParameterSet)
	                    .buildRequest() 
	                    .post();

// Called after each slice of the file is uploaded
final IProgressCallback callback = (current, max) -> System.out.println("Uploaded "+ current + " bytes of " + max +                         " total bytes");

final LargeFileUploadTask<AttachmentItem> uploadTask =  new LargeFileUploadTask<>(uploadSession, graphClient, fileStream,                         file.length(), AttachmentItem.class);

// upload (default: chunkSize is 5 MB)     
uploadTask.upload(0, null, callback);


In the example, you can see that first, an uploadSession is created via a POST request. A possible response to the POST request could look like the following:

HTML
 
HTTP/1.1 201 Created Content-type: application/json

{ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.uploadSession", 

"uploadUrl": "https://outlook.office.com/api/gv1.0/users('a8e8e219-4931-95c1-b73d-62626fd79c32@72aa88bf-76f0-494f-91ab-2d7cd730db47')/events('AAMkAGUwNjQ4ZjIxLTQ3Y2YtNDViMi1iZjc4LTMA=')/AttachmentSessions('AAMkAGUwNjQ4ZjIxLTAAA=')?authtoken=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFTeXQ1bXdXYVh5UFJ",

"expirationDateTime": "2021-12-27T14:20:12.9708933Z", 

"nextExpectedRanges": [ "0-" ] }


As a response to the POST request, you get an upload link uploadURL, which can be used to upload the attachment. What becomes clear here is that the upload of the attachment is done via an Outlook API and not via an MS Graph API! 

If you start the upload (uploadTask.upload(0, null, callback)) then it can happen that you get the following error:

Textile
 
401 : Unauthorized upon request. com.microsoft.graph.core.ClientException: 
Error code: InvalidAudienceForResource Error message: 
The audience claim value is invalid for current resource. 
Audience claim is 'https://graph.microsoft.com/', request url is 
'https://outlook.office.com/api/v2.0/Users...'.


To understand this error in more detail, consider the following code snippet of the AuthenticationHandler of the MS Graph Java SDK:

Java
 
CompletableFuture<String> future = this.authProvider.getAuthorizationTokenAsync(originalRequest.url().url());
String accessToken = (String)future.get();
return accessToken == null 
  	? chain.proceed(originalRequest) 
  	: chain.proceed(originalRequest.newBuilder().addHeader("Authorization", "Bearer " + accessToken).build());


For each request via graphClient, an authorization header with an access token for the MS Graph API is appended. Also for each PUT request sent via the upload link, uploadURL
is sent to the Outlook API. This leads to the authorization problem because the authentication credentials are not valid for the Outlook API (return code 401).

One possible solution to avoid the problem with the audience claim value is to customize the authentication provider. To do this, consider this example: for the client graphClient, the getAuthenticationProvider() method defines the provider. The following implementation of this method generates a provider that first checks whether or not the host for the request is the MS Graph API. If not, then a CompletableFuture with a null value is returned.

Java
 
private IAuthenticationProvider getAuthenticationProvider() { 
       return new IAuthenticationProvider() {  
        	private String hostNameToCheck = "graph";
            @Override 
			public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) {
						CompletableFuture<String> future = new CompletableFuture<>();
	                	if(requestUrl.getHost().toLowerCase().contains(hostNameToCheck)){
                    		future.complete(getToken());
		                } else {
		                    future.complete(null);
		                }
                		return future;
            }
	  };
}


Due to the fact that a null value is returned, each request to the MS Graph API is appended by an authorization header and each request to the Outlook API remains unchanged. For that reason, the InvalidAudienceForResource error can be avoided.

From the example we can see that the problem is not caused by the service library: it's caused by the authorization library. The problem is known and is not expected to be fixed in the near future. For this reason, this workaround becomes a permanent solution.

Read Outlook Contacts With Two Fax Numbers (Missing SingleValueLegacyExtendedProperty)

According to the design of contacts,  it is not foreseen that neither professional nor private fax numbers can be created for contacts with Microsoft Graph REST API v1.0.

However, the MS Graph API allows the possibility to extend the data model of running instances of a resource with custom data fields. Consider the following example where a contact is generated and stored with both professional and personal fax numbers:

Java
 
public class FaxNumbersSample implements SampleClass {

    private static final String PRIVATE_FAX_NUMBER = "privateFaxNumber";
    private static final String BUSINESS_FAX_NUMBER = "businessFaxNumber";
    private static final String PUBLIC_GUID_STRING = "00020329-0000-0000-c000-000000000046";
    private static final String NAME = "} Name ";

    @Override
    public void run() {
        final String primaryUser = "office365userEmail";
        final String REQUEST_URI = "https://graph.microsoft.com/v1.0/users/"
                + primaryUser
                + "/contacts"
                + "/";

        final GraphServiceClient<okhttp3.Request> graphClient =
                GraphServiceClient
                        .builder()
                        .authenticationProvider(getAuthenticationProvider())
                        .buildClient();

        Contact contact = new Contact();
        List<SingleValueLegacyExtendedProperty> pageContents = new ArrayList<>();
        pageContents.add(setProperty(BUSINESS_FAX_NUMBER, "001"));
        pageContents.add(setProperty(PRIVATE_FAX_NUMBER, "002"));
        if (!pageContents.isEmpty()) {
            contact.singleValueExtendedProperties =
                    new SingleValueLegacyExtendedPropertyCollectionPage(pageContents,
                    new SingleValueLegacyExtendedPropertyCollectionRequestBuilder(
                            REQUEST_URI, graphClient, new ArrayList<>()));
        }

        contact.givenName = "Pavel";
        contact.surname = "Bansky";
        LinkedList<EmailAddress> emailAddressesList = new LinkedList<EmailAddress>();
        EmailAddress emailAddresses = new EmailAddress();
        emailAddresses.address = "pavelb@fabrikam.onmicrosoft.com";
        emailAddresses.name = "Pavel Bansky";
        emailAddressesList.add(emailAddresses);
        contact.emailAddresses = emailAddressesList;
        LinkedList<String> businessPhonesList = new LinkedList<String>();
        businessPhonesList.add("+1 732 555 0102");
        contact.businessPhones = businessPhonesList;

        //response to the POST request doesn't contain the extra properties
        contact = graphClient
                .users(primaryUser)
                .contacts()
                .buildRequest()
                .post(contact);

        //to get all properties we need to expand the GET request
        contact = graphClient
                .users(primaryUser)
                .contacts(contact.id)
                .buildRequest()
                .expand(new StringBuilder()
                        .append("singleValueExtendedProperties($filter=id eq 'String {")
                        .append(PUBLIC_GUID_STRING)
                        .append(NAME)
                        .append(PRIVATE_FAX_NUMBER)
                        .append("'")
                        .append(" or id eq 'String {")
                        .append(PUBLIC_GUID_STRING)
                        .append(NAME)
                        .append(BUSINESS_FAX_NUMBER)
                        .append("'")
                        .append(" or id eq 'String {")
                        .append(PUBLIC_GUID_STRING)
                        .append(NAME)
                        .append(ACADEMIC_TITLE)
                        .append("')").toString())
                .get();

    }

    private SingleValueLegacyExtendedProperty setProperty(String propertyName, String propertyValue) {
        final SingleValueLegacyExtendedProperty property = new SingleValueLegacyExtendedProperty();
        property.id = new StringBuilder()
                .append("String {")
                .append(PUBLIC_GUID_STRING)
                .append(NAME)
                .append(propertyName).toString();
        property.value = propertyValue;
        return property;
    }

    @Override
    public IAuthenticationProvider getAuthenticationProvider() {
        return new IAuthenticationProvider() {
            private String hostNameToCheck = "graph";
            @Override
            public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) {
                CompletableFuture<String> future = new CompletableFuture<>();
                if(requestUrl.getHost().toLowerCase().contains(hostNameToCheck)){
                    future.complete(getToken());
                } else{
                    future.complete(null);
                }
                return future;
            }
        };
    }

    @Override
    public String getToken() {
        return null;
    }

}


The example shows that the standard data model can be extended by additional properties (SingleValueLegacyExtendedProperty). When creating the additional data fields, a fixed format for the ID must be adhered to. This can look as follows:

Plain Text
 
String {00020329-0000-0000-c000-000000000046} Name faxNumber


Thereby 00020329-0000-0000-c000-0000000046 is called GUID, which can be used for all characters in general. Beware: the return value of the POST request doesn't contain the extra fields. If you want to read all fields, then you have to do this via an additional extended GET request.  In the example above, the GET request is extended by the following filter:

Plain Text
 
singleValueExtendedProperties($filter=id eq 'String {00020329-0000-0000-c000-000000000046} Name businessFaxNumber' or id eq 'String {00020329-0000-0000-c000-000000000046} Name privateFaxNumber'


Although by default fax numbers cannot be created via the Microsoft Graph REST API v1.0, the SingleValueLegacyExtendedProperty class allows storing fax numbers. This solution path has the disadvantage that fax numbers are not automatically displayed in Outlook. However, Microsoft plans to include fax numbers in the default model of MS Graph in the future, which will not only simplify the storage of fax numbers but also fax numbers will be displayed in Outlook automatically.

Graph (Unix)

Opinions expressed by DZone contributors are their own.

Related

  • Knowledge Graph Embeddings and NLP Innovations
  • Blue Skies Ahead: An AI Case Study on LLM Use for a Graph Theory Related Application
  • Graph API for Entra ID (Azure AD) Object Management
  • Building Neural Networks With Automatic Differentiation

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!