This blog discusses how to obtain access token in Dynamics 365 Finance and Operations to enable data access of another application via APIs that use OAuth 2.0 authorization protocol. First of all lets see the definitions of some of the terms we are going to use here:
- Access token
- Use of access token in API
- OAuth2.0 authentication in web API
Access token
Access tokens are used in token-based authentication to allow an application to access an API. The application receives an access token after a user successfully authenticates and authorizes access, then passes the access token as a credential when it calls the target API. The passed token informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that was granted during authorization.
Use of access token in API
API tokens allow a user to authenticate with cloud apps and bypass two-step verification and single sign-on, and retrieve data from the instance through REST APIs. Token controls allow admins to view and revoke the use of API tokens by their managed accounts. (In this article we are discussing particularly OAuth2.0 Code Grant Authorization to enable our application to obtain Access Token).
OAuth 2.0 authentication in Web API
OAuth2 is the preferred method of authenticating access to the API. OAuth2 allows authorization without the external application getting the user’s email address or password. Instead, the external application gets a token that authorizes access to the user’s account.
To acquire the access token in Dynamics 365 Finance and Operations, there are two steps; first is to generate the authorization code and second is to use the authorization code to request for the token.
(1) Generate Authorization code
The authorization code is a temporary code that the client will exchange for an access token. The code itself is obtained from the authorization server where the user gets a chance to see what the information the client is requesting, and approve or deny the request.
The authorization code flow offers a few benefits over the other grant types. When the user authorizes the application, they are redirected back to the application with a temporary code in the URL. The application exchanges that code for the access token. When the application makes the request for the access token, that request can be authenticated with the client secret, which reduces the risk of an attacker intercepting the authorization code and using it themselves. This also means the access token is never visible to the user or their browser, so it is the most secure way to pass the token back to the application, reducing the risk of the token leaking to someone else.
Below code is to request the authorization code by sending the authURL and client Id provided by the application that needs to be integrated with D365 FnO. Use browser.navigate which navigates/loads the info at the specified URL into a new browser window.
public void generateAuthCode() { Browser browser; str authURL = "https://console-sandbox.foodics.com/authorize?response_type=code&client_id=%1&redirect_uri=%2"; str clientId = "123456789123456789123546789123456"; str callbackURL = "https://usnconeboxax1aos.cloud.onebox.dynamics.com"; browser = new Browser(); str url = strFmt(authURL, clientId, callbackURL); browser.navigate(url); }
The authorization URL is usually in a format such as:
‘https://console-sandbox.abc.com/authorize?response_type=code&client_id=123456-78910-111213-aaa0b60d5c0a&redirect_uri=https://usnconeboxax1aos.cloud.onebox.dynamics.com/’
Note: You will most likely first need to register your redirect URL at the service before it will be accepted. This also means you can’t change your redirect URL per request. Instead, you can use the state parameter to customize the request. See below for more information.
Once the authorization code is returned in the browser window, it will be used to get the access token.
(2) Generate access token using authorization code
Following parameters are required in the body of our request.
- grant_type: this is to identify the grant type as OAuth 2.0 supports multiple grant types. Here we are using authorization_code: as grant_type.
- code: the authorization code your app obtained upon user authorization by using above step.
- client_id: your app client id, given to you by the other application.
- client_secret: your app secret, given to you by the other application.
- redirect_uri: must be the same redirect URI you redirected the user to during user authorization.
Following is the code to acquire access token.
First create a data contract class to hold the parameters required for token generation.
[DataContract] public class GenerateAccessTokenDataContract { [DataMember("grant_type")] public str paramGrantType(str _grantType = grantType) { grantType = _grantType; return grantType; } [DataMember("code")] public Str1260 paramAuthCode(Str1260 _authCode = authCode) { authCode = _authCode; return authCode; } [DataMember("client_id")] public str paramClientId(str _clientId = clientId) { clientId = _clientId; return clientId; } [DataMember("client_secret")] public str paramClientSecret(str _clientSecret = clientSecret) { clientSecret = _clientSecret; return clientSecret; } [DataMember("redirect_uri")] public str paramRedierectUri(str _redierectUri = redierectUri) { redierectUri = _redierectUri; return redierectUri; } [DataMember("access_token")] public Str1260 paramAccessToken(Str1260 _accessToken = accessToken) { accessToken = _accessToken; return accessToken; } }
Create a new class named GenerateAccessToken and write following code:
public class GenerateAccessToken extends Runbase { str authorizationCode; str jsonString; public static void main(Args _args) { GenerateAccessToken generateAccessToken; generateAccessToken = new GenerateAccessToken(); if (generateAccessToken.prompt()) generateAccessToken.runOperation(); } public void run() { ttsbegin; this.setAccessToken(); ttscommit; } public void setAccessToken() { Newtonsoft.Json.Linq.JObject jObject; str resultString; str clientId = "123456789123456789123546789123456"; str secretKey = "abc1234def5678"; str callbackURL = "https://usnconeboxax1aos.cloud.onebox.dynamics.com"; str accessTokenURL = "https://api-sandbox.abc.com/oauth/token"; GenerateAccessTokenDataContract generateAccessTokenDataContract = new GenerateAccessTokenDataContract (); Microsoft.Dynamics.Client.ServerForm.Contexts.SessionContext sessionContext; System.Uri currentUrl = null; sessionContext = Microsoft.Dynamics.Client.ServerForm.Contexts.SessionContext::get_Current(); if (sessionContext) { currentUrl = sessionContext.get_RequestUrl(); } Str1260 authUrl = currentUrl.ToString(); GenerateAccessToken::splitAuthCode(authUrl); generateAccessTokenDataContract.paramGrantType("authorization_code"); generateAccessTokenDataContract.paramAuthCode(authenticationCode); generateAccessTokenDataContract.paramClientId(clientId); generateAccessTokenDataContract.paramClientSecret(secretKey); generateAccessTokenDataContract.paramRedierectUri(callbackURL); jsonString = this.serializeAccessTokenJson(generateAccessTokenDataContract); jObject = Newtonsoft.Json.Linq.JObject::Parse(jsonString); jObject.Property("access_token").Remove(); resultString = this.callApi(jsonString, accessTokenURL, accessTokenURL, "Post"); generateAccessTokenDataContract = this.deserializeCustJson(resultString); str token = fdsAcessTokenDataContract.paramAccessToken(); info(token); } static void splitAuthCode(Str1260 _authUrl) { List strlist=new List(Types::String); ListIterator iterator; str _Value; int counter = 0; strlist=strSplit(_authUrl,"="); iterator = new ListIterator(strlist); while(iterator.more()) { counter++; _Value =iterator.value(); if(counter == 2) { authorizationCode = strReplace(_Value, "&cmp", ""); } iterator.next(); } } public str serializeAccessTokenJson(GenerateAccessTokenDataContract _datContract) { jsonstring = FormJsonSerializer::serializeClass(_datContract); return jsonstring; } public GenerateAccessTokenDataContract deserializeCustJson(str _jsonstring) { GenerateAccessTokenDataContract generateAccessTokenDataContract = FormJsonSerializer::deserializeObject(classNum(GenerateAccessTokenDataContract),_jsonstring); return generateAccessTokenDataContract; } public str callApi(str _jsonString, str _url, str, _accessTokenURL, str _urlType) { RetailCommonWebAPI webApi = RetailCommonWebAPI::construct(); System.IO.StringWriter stringWriter; Newtonsoft.Json.JsonTextWriter jsonWriter; System.Net.HttpWebRequest request; System.Net.HttpWebResponse response; System.IO.StreamWriter streamWrite; System.Net.ServicePoint servicePoint; CLRObject clrObj; System.Text.Encoding utf8; System.Exception ex; System.Byte[] byteArraynew; System.Net.WebHeaderCollection headers = new System.Net.WebHeaderCollection(); str msgStatusCode; str jsonmsg; str access_token; try { new InteropPermission(InteropKind::ClrInterop).assert(); str byteStr = strfmt('%1:%2', "USERNAME", "PASSWORD"); clrObj = System.Net.WebRequest::Create(_url); request = clrObj; request.set_Method(_urlType); request.set_KeepAlive(true); utf8 = System.Text.Encoding::get_UTF8(); byteArraynew = utf8.GetBytes(byteStr); byteStr = System.Convert::ToBase64String(byteArraynew); headers.Add('Authorization', strFmt(" Bearer %1", access_token)); request.set_Headers(headers); request.set_Accept("application/json"); request.set_ContentType("application/json"); servicePoint = request.get_ServicePoint(); servicePoint.set_Expect100Continue(false); stringWriter = new System.IO.StringWriter(); jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter); streamWrite = new System.IO.StreamWriter(request.GetRequestStream()); streamWrite.Write(_jsonString); streamWrite.Flush(); streamWrite.Close(); response = request.GetResponse(); System.IO.StreamReader streamRead = new System.IO.StreamReader(response.GetResponseStream()); jsonmsg = streamRead.ReadToEnd().ToString(); msgStatusCode = response.StatusCode.ToString(); } catch (Exception::CLRError) { ex = CLRInterop::getLastException().getbaseexception(); error(ex.get_message()); } return jsonmsg; } }
Note: Access token URL will be provided by the application than needs to be integrated with Dynamics 365 Finance and Operations. If your request to get access token is valid, the authorization server will return an access token otherwise it will return an error message.