how can i send a post request via Business Central? - dynamics-business-central

I would like to create a PostRequest in my Business Central Extension that authenticates me in my web service and returns me a token. I send my username and password in the body of the request to my web service and I also receive the token in JSON format in the body.I want to create the post request using HttpClient.
I use the following link as a template: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/methods-auto/httpcontent/httpcontent-data-type
procedure sendPostRequest(uri: Text)
var
client: HttpClient;
content: HttpContent;
contentHeaders: HttpHeaders;
response: HttpResponseMessage;
request: HttpRequestMessage;
begin
content.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json');
request.Content:= content;
request.SetRequestUri(uri);
request.Method := 'POST';
end;
procedure SetURLsToDefault(var MessagingServiceSetup: Record "Messaging Service Setup WMR")
begin
MessagingServiceSetup."Service URL" := '202.212.127:8800';
end;
And I have a couple of questions:
1) the basic url is 202.212.127:8800 for my API gateway. To be able to authenticate myself I have to access 202.212.127:8800/authenticate. Is there a method in which you can create urls?
2) how do I get my username and password in the content?
3) and how do I get the token and can I save it in the field?
can someone tell me how to get the PostRequest up and running?

Common method to create different URLs is like this:
Create a setup table
Create fields like "Base Url", User, Pass etc.
I propose this pattern for your code:
SendRequest(Method; Url; Body)
Begin
...
Couple of functions (Your Api and Auth):
Authenticate()
begin
Method = 'post';
Url = SetupTable."Base Url" + '/authenticate';
Body = (Use AL Json stack and incorporate your user pass)
SendRequest(Method; Url; Body);
end;
Function1()
begin
Method = 'get';
Url = SetupTable."Base Url" + '/apiPath-Function1';
Body = '';
SendRequest(Method; Url; Body);
end
Function2()
begin
Method = 'post';
Url = SetupTable."Base Url" + '/apiPath-Function2';
Body = (Use AL Json stack and incorporate your body structure);
SendRequest(Method; Url; Body);
end;
To get your user pass into the content you need to check the documentation of the Api you're trying to call. It's usually described in details, it can be a simple header for basic authentication or a complex Jwt.
For receiving a token, again you need to check your Api documentation first, but essentially after making a Rest call (like: client.Send(RequestMessage, ResponseMessage); inside your SendRequest method), you get a response back and you can use AL Json stack to carve information out.
This is a fine article on how to proceed:
https://jackmallender.com/2019/03/04/interacting-with-rest-apis-using-json-from-within-business-central-part-1-an-introduction-to-the-httpclient-data-type/

Basically a string could work as an url. Depends on what you want. It is good practice to have a setup for your web service calls, so I am with Babak. You can set up a table in which you store the links, credentials - whatsoever.
and 4) I suggest Waldos Rest App for web service calls. you can download the source here: https://github.com/waldo1001/waldo.restapp
It encapsulated the calls, has helper functions for json handling as well. Using the "REST Helper" Codeunit. You can break down your call to:
local procedure DoCallWebservice(URI: Text; User: Text; Pass: Text; var Token: Text);
var
RESTHelper: Codeunit "REST Helper WLD";
begin
RRESTHelper.Initialize('GET', URI);
RESTHelper.SetContentType('application/json');
RESTHelper.AddBody('{"user":"USERNAME","pass":"PASSWORD"}');
if RESTHelper.Send() then
Token := RESTHelper.GetResponseContentAsText();
end;
Obviously, you need to parse the response (JSONHelper) to your needs. Look at the code of the codeunit, it's more or less self explanatory.

Related

Token based authentication http interceptor

I would like your opinion in order to achieve something. I have an api and I want to restrictionate the access of the user. But short story long, in action login controller of page /login I generate the token for the user autheticated and I return it. Well, here comes one of my question/problem. It's mandatory in a action controller to return a response, and my token it's send in json format and it's displayed on browser, but I don't want this, I just want to keep in response or in something so that my angular part to take that token and to take care of it in its way. So my method login:
public function loginAction(Request $request)
{
$username= $this->get('security.token_storage')->getToken()->getUser()->getEmail();
$serviceUser = $this->container->get('ubb_user_login');
$tokenUser = $serviceUser->generateToken($username);
$response = new JsonResponse(array('Token' => $tokenUser));
return $response;
}
2nd problem. I do not know in angular where to extract the token. In a controller I tried something:
app.controller('loginApi', function ($scope, $http, $window){
$http.get(
'/api/user/login'
).then(function (success) {
$scope.token = success.data.Token;
$window.localStorage.setItem('Token', token); // it's okay used like this localStorage?
});
});
Here, I want only to take the token from /login action, to save it in local storage, and then to display it on every request using $http request interceptor. That part works. If I send a random string in header auth it's working and it gives me access to api:
function httpInterceptor() {
return {
request: function(config) {
config.headers.authorization = 'jdjdnnkdkd';
return config;
},
responseError: function(errorResponse) {
switch (errorResponse.status) {
case 403:
window.location = '/login';
break;
case 401:
window.location = '/login';
}
return $q.reject(errorResponse);
}
}
}
So, the problems I encounter:
1) How to send the token in order for angular to take it, but to not be displayed in the browser? Let's say that I have a menu that access the api, and if the user it's not authenticated,clicking on a link unauthenticated it's sending me on the login page, and after I auth with success, to redirect me on the menu?
2) After returning the token, where exactly to use it in angular? In which controller? And how to save it?
Much appreciation, thank you.
It seems like you need a separate api resource for working with the tokens. Have you tried using FOSOAuthServeBundle? It helps a lot with setting up oauth authentication, tokens etc.
In general you need to have a separate call for the token, i.e.:
angluar app makes request to a get token resource, a token is returned and temporarily stored in the angular app
use that token for each subsequent request - check the oauth bundle config on how to set which urls have to be oauth protected, but that bundle takes care of this for you
Regarding your angular issue, look at this q/a How to store authentication bearer token in browser cookie using AngularJS

Mailchimp RESTful API 3.0 HTTP Basic Auth

I'm trying to use the Mailchimp API version 3.0 with basic auth. I'm using Classic ASP.
The Json response is always: "API Key Missing".
Set HttpReq = Server.CreateObject("MSXML2.ServerXMLHTTP")
HttpReq.open "GET", "https://us4.api.mailchimp.com/3.0/", False
HttpReq.setRequestHeader "Content-Type", "application/json"
HttpReq.setRequestHeader "apikey", "xxxxxx"
HttpReq.send ""
Response.Write HttpReq.ResponseText
Set HttpReq = Nothing
I'm sending it as a header.
What am I doing wrong..?
The answer is:
HttpReq.setRequestHeader "Authorization", "apikey xxxxxx"
If you're trying to use Basic Auth, you need to actually follow the spec. You can build the header yourself using the wiki article as your guide, but the easiest thing is to just use your HTTP Library's built-in support for that. In your case, this will probably help.
To roll your own, you need two pieces of information. The first is the username the second is the password. For MailChimp v3.0, the username can be anything. I tend to use 'apikey' as the username. The password is the API Key itself. Let's say my API Key is 'xxxxxxxxxx-yyy'. Now, you Base 64 Encode the string apikey:xxxxxxxxxx-yyy. That gives me YXBpa2V5Onh4eHh4eHh4eHgteXl5. Now the header I create is:
Authorization: Basic YXBpa2V5Onh4eHh4eHh4eHgteXl5
The method you're using will work, but is very custom to MailChimp and might confuse future visitors to your code.
I see that the question was based on #C but I had the same problem with java.(I am new to java so fell free to edit my code).
public String get(String url) throws IOException {
HttpGet get = new HttpGet(url);
String apiEncode = "apikey:9590e52MyAPI8-us9";
String encoding = Base64.encodeBytes(apiEncode.getBytes());
get.setHeader("Authorization","Basic " + encoding );
HttpResponse response = http.execute(get);
if (response.getEntity() != null) {
return EntityUtils.toString(response.getEntity(), "UTF-8").trim();
} else {
throw new IOException(response.getStatusLine().toString());
}
}

Google OpenId Connect migration: getting the openid_id in ASP.NET app

I've gone through plenty of Google documentation and SO Q/A's but with no luck. I wonder if anyone has yet succesfully used the OpenId to OpenId Connect migration as advised by Google.
This is what we used to do:
IAuthenticationResponse response = _openid.GetResponse();
if (response != null) {
//omitted for brevity
} else {
IAuthenticationRequest req = _openid.CreateRequest("https://www.google.com/accounts/o8/id");
req.AddExtension(new ClaimsRequest
{
Country = DemandLevel.Request,
Email = DemandLevel.Request,
Gender = DemandLevel.Require,
PostalCode = DemandLevel.Require,
TimeZone = DemandLevel.Require
});
req.RedirectToProvider();
}
That was done using a version of DotNetOpenAuth that dates back a few years. Because Google has deprecated OpenId authentication we are trying to move over to OpenID Connect. The key question here is: can I somehow get my hands on the OpenId identifier (in the form of https://www.google.com/accounts/o8/id?id=xyz) using the latest version of DotNetOpenAuth library or by any other means?
I have tried the latest DotNetOpenAuth and I can get it to work but it gives me a new Id (this was expected). I have also tried the Javascript way by using this URL (line breaks for readibility):
https://accounts.google.com/o/oauth2/auth?
scope=openid%20profile%20email
&openid.realm=http://localhost/palkkac/
&client_id=//here is the client id I created in google developer console
&redirect_uri=http://localhost/palkkac/someaspxpagehere
&response_type=id_token%20token
I checked (using Fiddler) the realm value that we currently send using the old DotNetOpenAuth code and it is http://localhost/palkkac/. I've put the same realm in the url above. The redirect url starts with the realm value but it is not entirely the same.
When I redirect to a simple page that parses the id_token and decrypts it (using the https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=zyx endpoint) I get this:
audience "client id is here"
email "mikkark#gmail.com"
expires_in 3597
issued_at //some numbers here
issued_to "client id is here"
issuer "accounts.google.com"
user_id "here is a sequence of numbers, my id in the OpenID Connect format that is"
verified_email true
So there is no sign of the openid_id field that you would expect to find here, though the whole structure of the message seems different from the Google docs, there is no field titled sub, for example. I wonder if I'm actually using the wrong endpoint, parameters or something?
What I have been reading is the migration guide: https://developers.google.com/accounts/docs/OpenID. I skipped step 2 because it seemed like an optional step. In step 3 the field openid_id is discussed and I would like to get that to work as a proof-of-concept first.
We registered the app on Google in order to create the client id etc. There are now also numerous allowed redirect url's as well as javascript origins listed in the Google dev console. Let me know if those might mess up the system and I'll post them here for review.
Side note: we are supposed to be moving our app behind a strictly firewalled environment where we would need to open ports in order to do this on the server side. Therefore, a client-side Javascript solution to access Google combined with HTTPS and redirecting the result to the server would be prefered (unless there are other issues that speak against this).
There are other resources on SO regarding this same issue, although all of these seem to use different libraries on the server side to do the job and nobody seems to have made any attempts at using Javascript:
Here (https://stackoverflow.com/questions/22842475/migrating-google-openid-to-openid-connect-openid-id-does-not-match) I think the problem was resolved by setting the realm to be the same as in the old OpenId2.0 flow. This does not seem to work in my case.
over here the openid_id field is also missing, but the problem here is more about how to request the id_token from Google using libraries other than DotNetOpenAuth.
and in here there seem to be similar problems getting Google to return the openid_id field.
You can use the GoogleAuthentication owin middleware.
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
SignInAsAuthenticationType = signAs,
AuthenticationType = "Google",
ClientId = "xxx.apps.googleusercontent.com",
ClientSecret = "xx",
CallbackPath = PathString.FromUriComponent("/oauth2callback"),
Provider = new GoogleOAuth2AuthenticationProvider
{
OnApplyRedirect = context =>
{
context.Response.Redirect(context.RedirectUri + "&openid.realm=https://mydomain.com/"); // DotNetOpenAuth by default add a trailing slash, it must be exactly the same as before
}
},
BackchannelHttpHandler = new MyWebRequestHandler()
}
Then, add a new class called MyWebRequestHandler:
public class MyWebRequestHandler : WebRequestHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var httpResponse = await base.SendAsync(request, cancellationToken);
if (request.RequestUri == new Uri("https://www.googleapis.com/plus/v1/people/me")) return httpResponse;
var configuration = await OpenIdConnectConfigurationRetriever.GetAsync("https://accounts.google.com/.well-known/openid-configuration", cancellationToken); // read the configuration to get the signing tokens (todo should be cached or hard coded)
// google is unclear as the openid_id is not in the access_token but in the id_token
// as the middleware dot not expose the id_token we need to parse it again
var jwt = httpResponse.Content.ReadAsStringAsync().Result;
JObject response = JObject.Parse(jwt);
string idToken = response.Value<string>((object)"id_token");
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
try
{
SecurityToken token;
var claims = tokenHandler.ValidateToken(idToken, new TokenValidationParameters()
{
ValidAudience = "xxx.apps.googleusercontent.com",
ValidIssuer = "accounts.google.com",
IssuerSigningTokens = configuration.SigningTokens
}, out token);
var claim = claims.FindFirst("openid_id");
// claim.Value will contain the old openid identifier
if (claim != null) Debug.WriteLine(claim.Value);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return httpResponse;
}
}
If like me you found this not really straightforward, please help by upvoting this issue https://katanaproject.codeplex.com/workitem/359

How do I setup meteor to receive events from an external API?

I have registered my web address (let's just call it https://mywebaddress/callbacks) with this external API and it will now send me JSON when it completes an action. I don't need to initiate anything outbound to it, I just need to receive the JSON and store it.
EDIT:
JSON data will be receive via POST
Paul's link sent me in the right direction. (http://www.meteorpedia.com/read/REST_API).
Then I found the section titled "WebApp.connectHandlers and connect".
I used the code found there, but in my instance there was an error in the code. I had to change the first line from var connect = Npm.require('connect'); to var connect = Meteor.require('connect');
Here is the code below.
// necessary to parse POST data
var connect = Meteor.require('connect');
// necessary for Collection use and other wrapped methods
var Fiber = Npm.require('fibers');
WebApp.connectHandlers
.use(connect.urlencoded()) // these two replace
.use(connect.json()) // the old bodyParser
.use('/getUserProfile', function(req, res, next) {
// necessary for Collection use and other wrapped methods
Fiber(function() {
var userId = req.body.userId;
var user = Meteor.users.findOne(userId);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(user.profile));
}).run();
});
}
Then to test that this was working I used http://www.hurl.it/. I changed the destination to POST and added a header of content-type - application/json. I then pasted in the body some JSON that I knew came from balanced. If you need a tool to see what is actually being posted to your server you can use http://requestb.in/.

ASP.Net Web API - Authorization header blank

I am having to re-write an existing REST API using .NET (originally written with Ruby). From the client's perspective, it has to work exactly the same way as the old API - i.e. the client code mustn't need to change. The current API requires Basic Authentication. So to call the old API, the following works perfectly:-
var wc = new System.Net.WebClient();
var myCache = new CredentialCache();
myCache.Add(new Uri(url), "Basic", new NetworkCredential("XXX", "XXX"));
wc.Credentials = myCache;
var returnBytes = wc.DownloadData("http://xxxx");
(I have had to ommit the real URL / username / password etc for security reasons).
Now I am writing the new API using ASP.Net Web API with MVC4. I have a weird problem and cannot find anybody else with exactly the same problem. In order to support Basic Authentication, I have followed the guidelines here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
One thing, I put the code to "hook in the handler" in the Global.asax.cs file in the Application_Start() event (that wasn't explained so I guessed).
Anyway, if I call my API (which I have deployed in IIS) using the above code, the Authorization header is always null, and the above fails with 401 Unauthorized. However, if I manually set the header using this code, it works fine - i.e. the Authorization header now exists and I am able to Authenticate the user.
private void SetBasicAuthHeader(WebClient request, String userName, String userPassword)
{
string authInfo = userName + ":" + userPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
.......
var wc = new System.Net.WebClient();
SetBasicAuthHeader(request, "XXXX", "XXXX");
var returnBytes = wc.DownloadData("http://xxxx");
Although that works, it's no good to me because existing users of the existing API are not going to be manually setting the header.
Reading up on how Basic Authentication works, the initial request is meant to be anonymous, then the client is returned 401, then the client is meant to try again. However if I put a break point in my code, it will never hit the code again in Antony's example. I was expecting my breakpoint to be hit twice.
Any ideas how I can get this to work?
You're expecting the right behavior. System.Net.WebClient does not automatically include the Authorization headers upon initial request. It only sends them when properly challenged by a response, which to my knowledge is a 401 status code and a proper WWW-Authenticate header. See here and here for further info.
I'm assuming your basic authentication handler is not returning the WWW-Authenticate header and as such WebClient never even attempts to send the credentials on a second request. You should be able to watch this in Fiddler or a similar tool.
If your handler did something like this, you should witness the WebClient approach working:
//if is not authenticated or Authorization header is null
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
response.StatusCode = HttpStatusCode.Unauthorized;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"www.whatever.com\"");
return response;
});
//else (is authenticated)
return base.SendAsync(request, cancellationToken);
As you noticed, if you include the Authorization headers on every request (like you did in your alternative approach) then your handler already works as is. So it may be sufficient - it just isn't for WebClient and other clients that operate in the same way.

Resources