5

I'm trying to create an application using Angular that can connect to Dynamics CRM via the Web API. These are the steps I have followed:

1. Registered a native application in Azure, granted the required delegated permissions and updated the manifest to allow implicit flow.

2. Created an Application User in CRM, setting its Application ID equal to the Client ID of my Azure registered application. Assigned my Application User a custom Security Role.

3. Cloned many Angular 2 quickstart Git repositories which authenticate with Azure AD via ADAL such as this one.

4. Updated the cloned code's adal config, setting my tenant, clientId, redirectUri and endpoints.

So far this has been successful. I'm able to launch the application and login via my browser either as my Application User or a different CRM User which is a part of Azure AD. This returns a token.

5. Attempt to send an http.get to either v8.0 or v8.2 (I'm told that v8.2 does not support cross-domain calls):

getEntities(): Promise<any> {
    let token = this.adalService.getCachedToken(this.adalService.config.clientId);
    let headers = new Headers({
        'Authentication': 'Bearer ' + token,
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=utf-8',
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0'
    });
    let options = new RequestOptions({ headers: headers });

    return this.http.get(`${crmURL}/api/data/v8.2/accounts`, options)
        .toPromise()
        .then((res) => { return res; })
        .catch((e) => { console.error(e); });
}

6. Receive this error message:

enter image description here

It reads:

No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://localhost:3000' is therefore not allowed access. 
The response had HTTP status code 401.

Looking at my Chrome browser's network tab, I receive two responses:

Response 1

General

Request URL:https://ms-dyn365-prevxxxxxx/api/data/v8.2/accounts
Request Method:OPTIONS
Status Code:200 OK
Remote Address:104.44.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade

Headers

Access-Control-Allow-Headers:authentication,content-type,odata-maxversion,odata-version
Access-Control-Allow-Methods:GET
Access-Control-Allow-Origin:http://localhost:3000
Access-Control-Expose-Headers:Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
Access-Control-Max-Age:3600
Content-Length:0
Date:Thu, 13 Apr 2017 10:08:01 GMT
Server:Microsoft-IIS/8.5
Set-Cookie:crmf5cookie=!NDyiupL55lrWWLtPQKTK52dwbxk9wdEAHeCiec0/z/7x9KWXe2dVIdQCGvL0S/HAp7F3N0OGfeWf/70=;secure; path=/
Strict-Transport-Security:max-age=31536000; includeSubDomains
Vary:Origin
X-Powered-By:ASP.NET

Response 2

General

Request URL:https://ms-dyn365-prevxxxxx.crm4.dynamics.com/api/data/v8.2/accounts
Request Method:GET
Status Code:401 Unauthorized
Remote Address:104.xx.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade

Headers

Cache-Control:private
Content-Length:49
Content-Type:text/html
Date:Thu, 13 Apr 2017 10:08:01 GMT
REQ_ID:b2be65bc-xxxx-4b34-xxxx-5c39812650xx
Server:Microsoft-IIS/8.5
Set-Cookie:ReqClientId=xxxxxxxx-70b5-45f9-9b84-30f59481bxxx; expires=Wed, 13-Apr-2067 10:08:01 GMT; path=/; secure; HttpOnly
Strict-Transport-Security:max-age=31536000; includeSubDomains
WWW-Authenticate:Bearer authorization_uri=https://login.windows.net/xxxxxxxx-87e4-4d81-8010-xxxxxxxxxxxxx/oauth2/authorize, resource_id=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/
X-Powered-By:ASP.NET

Note: I am able to successfully access the Web API via Postman:

1. I enter https://www.getpostman.com/oauth2/callback as a Callback URL in Azure for my Application.

2. I open Postman, set the parameters as follows and press Request Token:

Token Name: Token
Auth URL: https://login.windows.net/common/oauth2/authorize?resource=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com
Access Token URL: https://login.microsoftonline.com/common/oauth2/token
Client ID: xxxxxxxx-ebd3-429c-9a95-xxxxxxxxxxxx
Callback URL: https://www.getpostman.com/oauth2/callback
Grant Type: Authorization Code

3. This opens a web page in which I login.

4. A token is returned which I add to a Postman GET header:

Content-Type: application/json
Authorization: Bearer eyJ0eXAiO...

5. Send a GET in Postman:

GET https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/api/data/v8.2/accounts

6. Accounts are successfully returned.

If I use the same token within my Application I still receive a 401 error.

Dave Clark
  • 2,243
  • 15
  • 32
  • When you registered your application in Azure, have you set "http://localhost:3000" as "Sign-on URL"? – Pawel Gradecki Apr 13 '17 at 08:17
  • @PawelGradecki I can't actually see an option for "Sign-on URL" in Azure (I'm using the newer Azure Portal). I can see "Home Page" and "Redirect URIs" both of which are set to "localhost:3000". – Dave Clark Apr 13 '17 at 09:27
  • Any other query is working? For example have you tried to obtain Accounts? Because currently you are querying for metadata, my guess is that this may not be possible even with CORS (although I did not find any information to support this assumption) – Pawel Gradecki Apr 13 '17 at 09:35
  • also you should use "application/json" not "application/jsonp" in your get request – Pawel Gradecki Apr 13 '17 at 09:38
  • @PawelGradecki I have tried _/api/data/v8.2/accounts_ and receive the same error. `application/jsonp` was a mistake in my question, my code actually uses `json`, sorry! I've now edited my question. – Dave Clark Apr 13 '17 at 10:10

1 Answers1

3

The Access-Control-Allow-Origin indicates the issue is caused by cross domain.

Normally, we can use JSONP or set Access-Control-Allow-Origin header on the server. And if both JSONP and header is not able to set since the service is provider by the third-party, we can also create a service proxy which enable to allow called from the specific orignal.

More detail about the AJAX cross domain issue, you can refer this thread.

Update

After the further investigation, the issue is a server-side issue which relative to the specific version of REST requesting.

The 8.2 version of REST doesn't support cross domain at present. As a workaround we can use 8.0 which works well for me like the figure below:

enter image description here

Append the code demo to test:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <base href="/">
    <title></title>
    <script src="node_modules\angular\angular.js"></script>

</head>
<body>
    <div ng-app="myApp">
        <div ng-controller="HomeController">
            <ul class="nav navbar-nav navbar-right">            
                     <li><a class="btn btn-link" ng-click="listAccounts()">List account info</a></li>
            </ul>
           <div ng-repeat="account in accounts">
                    <span>name:</span><span>{{account.name}}</span>
           </div>
        </div>
    </div>

    <script>
    var myApp = angular.module('myApp',[]);

    myApp.controller('HomeController', ['$scope', '$http',
                            function ($scope, $http){

                                    $scope.listAccounts=function(){

                                        var req = {
                                        method: 'GET',
                                        url: 'https://{domain}.crm.dynamics.com/api/data/v8.0/accounts',
                                        headers: {
                                        'authorization': 'Bearer eyJ0eXAiO...'
                                        }
                                        };
                                       $http(req).then(function(response){
                                            $scope.accounts=response.data.value
                                       }, function(){

                                       });
                                    }
                            }]);
    </script>
</body>
</html>

Append test code sample for Angular 2:

https://github.com/VitorX/angular2-adaljs-crm

Community
  • 1
  • 1
Fei Xue
  • 14,369
  • 1
  • 19
  • 27
  • Thank you for your answer. `Access-Control-Allow-Origin` should be enabled on the server (which I have no control over) because it is Dynamics 365 hosted in Azure. I'd prefer not to use JSONP because I know it is possible to use JSON; Microsoft do so in their example [here](https://msdn.microsoft.com/en-gb/library/mt595797.aspx). – Dave Clark Apr 13 '17 at 09:22
  • 1
    @DaveClark Based on the document you mentioned, it used CORS for the Same-Origin policy. And the header in your code was set to **JSONP** , please modify it as the code sample provide from the [link](https://msdn.microsoft.com/en-gb/library/mt595797.aspx) you mentioned. – Fei Xue Apr 13 '17 at 09:33
  • Oops, that was a mistake in my question (edited now). My code's header uses JSON. – Dave Clark Apr 13 '17 at 10:02
  • 1
    @DaveClark The issue is relative to the specific version of REST, please see the update and let me know if it helps. – Fei Xue Apr 14 '17 at 07:51
  • Thank you that's useful to know. My code's now using _v8.0_ however the same error message is received. – Dave Clark Apr 18 '17 at 07:03
  • Since the code works well for me, you may share a runnable code sample to help to reproduce this issue. I also update the post to add the test code sample for your reference. – Fei Xue Apr 18 '17 at 07:09
  • My code differs from yours in that yours uses AngularJS whereas mine uses Angular 2 and is written in TypeScript. My code is as straightforward as cloning [this GitHub repo](https://github.com/ranveeraggarwal/ng2-adal-QuickStart) and then editing the file _secret.service.ts_ file as required. – Dave Clark Apr 18 '17 at 10:38
  • 1
    Based on my understanding, the issue is server side issue, should not be relative to the client. I am testing it using the CRM from Office 365 and both Angular 1 and Angular 2 works well for me. For this issue you may check whether the server-side support the CORS. If not you may develop a web API as the proxy as a workaround. – Fei Xue Apr 20 '17 at 08:18
  • Thank you @Fei Xue - MSFT. I will research how to develop a Web API proxy. If you were able to post your working code which authenticates with Dynamics CRM from Angular 2 that would serve as an answer to this post. – Dave Clark Apr 20 '17 at 08:29
  • 1
    @DaveClark I have linked the test code sample for your reference. The code sample is only for test the CORS issue, it can not work when you click the **Get message**, but you can check the successful request through the **newwork** tab of Chrome. – Fei Xue Apr 20 '17 at 09:02
  • Your code sample works! Using it I'm able to authenticate and query the Web API to retrieve data. Many thanks for your help. If it's ok I'll post a link to the [GitHub repo](https://github.com/VitorX/angular2-adaljs-crm) on my other similar question. – Dave Clark Apr 21 '17 at 10:45