PeterKaagman has asked for the wisdom of the Perl Monks concerning the following question:

Hi there,

Not really sure this is an actual Perl question. But knowing your wisdom... I'll give a shot here.

Thing is I need to interact with MS Graph. Actually got it working in NodeJS, but I really don't like NodeJS... running back home... Perl

I've got an App registration in Azure to retrieve groups information. Got the APP_ID, APP_Secret and Tenant_id... these work just fine with Node. For Perl it seems I have a choice of cURL or LWP. Allthough I would prefer LWP I have chosen cURL for the moment. Did some work with it and there is more info on cURL/MS Graph than LWP/MS Graph.

This is the code I came up with:

#! /usr/bin/perl -W use strict; use WWW::Curl::Easy; use Data::Dumper; use JSON; use Config::Simple; use FindBin; #use lib "$FindBin::Bin/../lib"; my %config; Config::Simple->import_from("$FindBin::Bin/groups.cfg",\%config) or di +e("No config: $!"); my $c = WWW::Curl::Easy->new(); sub login_app { # {{{1 my $response; my $response_body; print "==LogIn==\n"; my @Headers = ( "Content-Type: application/x-www-form-urlencoded" ); $c->setopt(CURLOPT_URL, "https://$config{'LOGIN_ENDPOINT'}/$config +{'TENANT_ID'}/oauth2/token"); $c->setopt(CURLOPT_HEADER, "0"); $c->setopt(CURLOPT_WRITEDATA, \$response_body); $c->setopt(CURLOPT_SSL_VERIFYHOST, "1"); $c->setopt(CURLOPT_SSL_VERIFYPEER, "1"); $c->setopt(CURLOPT_POSTFIELDS, "grant_type=client_credentials&clie +nt_id=$config{'APP_ID'}&client_secret=$config{'APP_PASS'}"); $c->setopt(CURLOPT_HTTPHEADER, \@Headers); $c->setopt(CURLOPT_CUSTOMREQUEST, 'POST'); my $retcode = $c->perform(); print "Return code login: $retcode\n"; if ($retcode == 0){ my $response = decode_json($response_body); #print Dumper $response; return $response; }else{ print("An error occured: $retcode\n".$c->strerror($retcode)." +".$c->errbuf."\n"); return 0; } }# }}} sub fetch { my $token = shift; my $url = shift; my $method = shift; my $response; my $response_body; print "==Fetch==\n"; print "$url\n"; print "$token\n"; my @Headers = ( "Authorization: Bearer $token", 'Content-Type: application/json' ); #print Dumper \@Headers; $c->setopt(CURLOPT_URL, "$url"); $c->setopt(CURLOPT_HEADER, "0"); $c->setopt(CURLOPT_WRITEDATA, \$response_body); $c->setopt(CURLOPT_SSL_VERIFYHOST, "0"); $c->setopt(CURLOPT_SSL_VERIFYPEER, "0"); $c->setopt(CURLOPT_HTTPHEADER, \@Headers); $c->setopt(CURLOPT_CUSTOMREQUEST, $method); my $retcode = $c->perform(); print "Return code fetch: $retcode\n"; if ($retcode == 0){ my $response = decode_json($response_body); return $response; }else{ print("An error occured: $retcode\n".$c->strerror($retcode)." +".$c->errbuf."\n"); return 0; }} my $token_request = login_app(); if ($token_request){ if ($$token_request{'access_token'}){ my $url = "https://$config{'GRAPH_ENDPOINT'}/v1.0/groups"; my $groups = fetch($$token_request{'access_token'},$url,'GET') +; print Dumper $groups; }else{ print "Geen token\n"; print Dumper $token_request; } }

This is based on a pure cURL example which I found here

Running this code gets me the following output:

Bareword "Types::Serialiser::Error::" refers to nonexistent package at + /usr/share/perl5/Types/Serialiser.pm line 136. Bareword "Types::Serialiser::Error::" refers to nonexistent package at + /usr/share/perl5/Types/Serialiser.pm line 147. ==LogIn== Return code login: 0 ==Fetch== https://graph.microsoft.com/v1.0/groups eyJ0eXAiOi[edited out most of the token]eRppGoilg Return code fetch: 0 $VAR1 = { 'error' => { 'innerError' => { 'client-request-id' => 'f49ce +921-3ceb-4613-a901-3eb75a9126a2', 'date' => '2023-01-18T10:52:4 +3', 'request-id' => 'f49ce921-3ce +b-4613-a901-3eb75a9126a2' }, 'code' => 'InvalidAuthenticationToken', 'message' => 'Access token validation failure. +Invalid audience.' } };

I am able to get a token, but I cannot make requests with that token. (kinda ignored the serializer warnings)

Googling on the error codes given does not help a whole lot. I get things like wrong App registrations. But I'm pretty sure that part is ok. I suspect there is something wrong with how I put the token in the header. Below the cURL snippet on which I based my code for the groups request:

curl -X GET -H "Authorization: Bearer [TOKEN]" -H "Content-Type: appli +cation/json" https://management.azure.com/subscriptions/[SUBSCRIPTION +_ID]/providers/Microsoft.Web/sites?api-version=2016-08-01

Kinda at a loss. Am aware this smell like a MS Graph issue, but got good hints here in the past. Any help, even a RTFM (with pointer to the manual ;)), wil be greatly appreciated.

BTW
Did indeed try to get it to work with LWP. Was able to get a token but had some difficulty composing a header with the token

Regards

Peter

Replies are listed 'Best First'.
Re: Problems with MS Graph
by Corion (Patriarch) on Jan 18, 2023 at 12:11 UTC

    If you have a working Curl incantation, I would always start out by converting it to Perl using my Curl to LWP converter.

    Running your Curl command gives me the following HTTP::Tiny code:

    #!perl use strict; use warnings; use HTTP::Tiny; my $ua = HTTP::Tiny->new( 'verify_SSL' => '1' ); my $res = $ua->request( 'GET' => 'https://management.azure.com/subscriptions/[SUBSCRIPTION_ID]/provider +s/Microsoft.Web/sites?api-version=2016-08-01', { headers => { 'Content-Type' => 'application/json', 'Accept' => '*/*', 'User-Agent' => 'curl/7.55.1', 'Authorization' => 'Bearer [TOKEN]' }, }, ); __END__ Created from curl command line curl -X GET -H "Authorization: Bearer [TOKEN]" -H "Content-Type: appli +cation/json" https://management.azure.com/subscriptions/[SUBSCRIPTION +_ID]/providers/Microsoft.Web/sites?api-version=2016-08-01
Re: Problems with MS Graph
by Discipulus (Canon) on Jan 18, 2023 at 12:07 UTC
    Hello PeterKaagman

    did you tried curl2lwp page?

    I get (selecting LWP::UserAgent as User-Agent module):

    #!perl use strict; use warnings; use LWP::UserAgent; my $ua = LWP::UserAgent->new( 'send_te' => '0' ); my $r = HTTP::Request->new( 'POST' => 'https://login.microsoftonline.com/[TENANT_ID]/oauth2/to +ken', [ 'Accept' => '*/*', 'User-Agent' => 'curl/7.55.1', 'Content-Length' => '120', 'Content-Type' => 'application/x-www-form-urlencoded' ], "grant_type=client_credentials&client_id=[APP_ID]&client_secret=[PASSW +ORD]&resource=https\x253A\x252F\x252Fmanagement.azure.com\x252F" ); my $res = $ua->request( $r, );

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Problems with MS Graph
by bliako (Abbot) on Jan 19, 2023 at 11:34 UTC

    There is LWP::Curl which is the LWP API with the curl engine underneath.

    With LWP there are some debug options (untested, from memory):

    use LWP::ConsoleLogger::Easy qw( debug_ua ); use LWP::UserAgent; my $ua = LWP::UserAgent->new(...); debug_ua($ua, 10); ...

    This should print out the headers of any request. That said, if you have a working curl command-line then curl2lwp is your best bet to convert it to a working LWP-based perl script. It worked for me every time.

    btw, it does not look like you need cookie persistence (as per your curl command) but if you do:

    use LWP::UserAgent; use HTTP::Cookies; my $cookies = HTTP::Cookies->new(file =>'./cookies.txt'); my $ua = LWP::UserAgent->new(...); $ua->cookie_jar($cookies);

    Also, perhaps you want to try resolve the error Bareword "Types::Serialiser::Error::" refers to nonexistent package... by reinstalling Types::Serialiser? I am not sure how to resolve it.

    bw, bliako

      Did not notice you reply before, sorry for that, thanks for you trying to help

      I do not think I need cookie persistance either, but wil give it a go since all my other efforts seem to fail.

      Did try a pure curl approach in a bash script, pasting in the token I was able to get. That failed as wel. Bugs me that Node (using Axios) does not have this problem.

      Not really bothered by the serializer error atm... does not seem to interfere

        So, ***bash/curl*** and Perl fail where Node succeeds. I guess you need to watch the headers that Node sends and see how they compare to the other 2 methods. I have shown you how to check the headers with Perl. You need to do the same with Node. Or it may be easier via the browser if you have successfull access with that (developer tools, monitor the network request)

        Please update your posts to the fact that curl does not work (if I understood correctly). That's an important point.

Re: Problems with MS Graph
by PeterKaagman (Beadle) on Jan 18, 2023 at 12:21 UTC

    Thank you guys :D

    Did find a curl2php equiv and used it. Not a clue as why I did not look for a Perl version


    Edit:

    Alas, did help me a lot in understanding LWP, but I still get a 401 Unauthorized and a InvalidAuthenticationToken.

    Must be something else wrong

      I've done some MS Graph stuff for some time now. I just bundled it all and setup a module. Again, it's all done on top of LWP. Check LWP::UserAgent::msgraph, you can see the code and further understand LWP.