2

I'm trying to send notifications to my Windows Phone 8 app using an authenticated server, but I'm getting a 401 error whenever a notification is tried to be sent.

I've been following the instructions on the MSDN pages, namely http://msdn.microsoft.com/library/windows/apps/ff941099(v=vs.105).aspx where it says The Key-Usage value of the TLS certificate must be set to include client authentication. I've no idea what this means though and searching online doesn't give me any clues either.

It could be that, that is incorrect or it could be my code so that is shown below.

VB.NET CODE:

Private Async Sub ApplicationBarPin_Click(sender As Object, e As EventArgs)
    ' Holds the push channel that is created or found.
            Dim pushChannel As HttpNotificationChannel

            ' The name of our push channel.
            Dim channelName As String = "WindowsPhoneTrainTileNotification"

            ' Try to find the push channel.
            pushChannel = HttpNotificationChannel.Find(channelName)

            ' If the channel was not found, then create a new connection to the push service.
            If pushChannel Is Nothing Then
                pushChannel = New HttpNotificationChannel(channelName, "redsquirrelsoftware.co.uk")
                uri_timer = New DispatcherTimer
                uri_timer.Interval = TimeSpan.FromSeconds(5)
                AddHandler uri_timer.Tick, AddressOf UriTimerTick
                uri_timer.Start()

                ' Register for all the events before attempting to open the channel.
                AddHandler pushChannel.ChannelUriUpdated, AddressOf PushChannel_TileChannelUriUpdated
                AddHandler pushChannel.ErrorOccurred, AddressOf PushChannel_TileErrorOccurred

                pushChannel.Open()

                pushChannel.BindToShellTile()
            Else
                ' The channel was already open, so just register for all the events.
                AddHandler pushChannel.ChannelUriUpdated, AddressOf PushChannel_TileChannelUriUpdated
                AddHandler pushChannel.ErrorOccurred, AddressOf PushChannel_TileErrorOccurred

                Dim form As New MultipartFormDataContent()
                form.Add(New StringContent(Statics.getUserID), "userId")
                form.Add(New StringContent(pushChannel.ChannelUri.ToString()), "uri")
                form.Add(New StringContent(Statics.CurrentScheduleId), "scheduleId")

                Dim httpClient As HttpClient = New HttpClient()

                Dim response As HttpResponseMessage = Await httpClient.PostAsync("http://redsquirrelsoftware.co.uk/trains/push/WPTileSubscribe.php", form)
            End If

            ShellTile.Create(New Uri("/Route.xaml?scheduleId=" & scheduleId, UriKind.Relative), secondaryTile, True) 'Create SecondaryTile and pass querystring to navigation url.
        Catch ex As Exception
        End Try

    End If
End Sub

Private Async Sub PushChannel_TileChannelUriUpdated(sender As Object, e As NotificationChannelUriEventArgs)
    Dim form As New MultipartFormDataContent()
    form.Add(New StringContent(Statics.getUserID), "userId")
    form.Add(New StringContent(e.ChannelUri.ToString()), "uri")
    form.Add(New StringContent(Statics.CurrentScheduleId), "scheduleId")

    Dim httpClient As HttpClient = New HttpClient()

    Dim response As HttpResponseMessage = Await httpClient.PostAsync("http://redsquirrelsoftware.co.uk/trains/push/WPTileSubscribe.php", form)
End Sub

Private Sub PushChannel_TileErrorOccurred(sender As Object, e As NotificationChannelErrorEventArgs)
    MessageBox.Show("Error creating live tile, please try again")
End Sub

Private Async Sub UriTimerTick(sender As Object, e As EventArgs)
    Try
        If pushChannel.ChannelUri IsNot Nothing Then
            Dim form As New MultipartFormDataContent()
            form.Add(New StringContent(Statics.getUserID), "userId")
            form.Add(New StringContent(pushChannel.ChannelUri.ToString()), "uri")
            form.Add(New StringContent(Statics.CurrentScheduleId), "scheduleId")

            Dim httpClient As HttpClient = New HttpClient()

            Dim response As HttpResponseMessage = Await httpClient.PostAsync("http://redsquirrelsoftware.co.uk/trains/push/WPTileSubscribe.php", form)
            uri_timer.Stop()
        End If
    Catch ex As Exception
    End Try

End Sub

That works as expected and the URI is stored in my database (starting with HTTPS, and doesn't contain throttledthirdparty in the URI either, suggesting that this code works).

train_movements.php:

<?php
include 'WindowsPhoneTilePush.php';
$WPN = new WPN($packageSID, $clientSecret);
$xml_data = $WPN->build_tile_xml($sched_id, strtoupper($loc), "$eventTypeStr $dt (". strtolower($variation_status) . ")");
$WPNResponse = $WPN->post_tile($uri, $xml_data);
if($WPNResponse->error == true) {
    $my_file = $logFile;
    $handle = fopen($my_file, 'a') or die('Cannot open file:  '.$my_file);
    $data = $WPNResponse->httpCode . ":" . $WPNResponse->message . "\n";
    fwrite($handle, $data);
}
?>

WindowsPhoneTilePush.php: (adapted from here)

<?php
class WPNTypesEnum{       
    const Toast = 'wns/toast';
    const Badge = 'wns/badge';
    const Tile  = 'wns/tile';
    const Raw   = 'wns/raw';
}                         

class WPNResponse{
    public $message = '';
    public $error = false;
    public $httpCode = '';

    function __construct($message, $httpCode, $error = false){
        $this->message = $message;
        $this->httpCode = $httpCode;
        $this->error = $error;
    }
}

class WPN{            
    private $access_token = '';
    private $sid = '';
    private $secret = '';

    function __construct($sid, $secret){
        $this->sid = $sid;
        $this->secret = $secret;
    }

    private function get_access_token(){
        if($this->access_token != ''){
            return;
        }

        $str = "grant_type=client_credentials&client_id=$this->sid&client_secret=$this->secret&scope=notify.windows.com";
        $url = "https://login.live.com/accesstoken.srf";

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
        curl_setopt($ch, CURLOPT_POSTFIELDS, "$str");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        curl_close($ch);                       

        $output = json_decode($output);

        if(isset($output->error)){
            throw new Exception($output->error_description);
        }

        $this->access_token = $output->access_token;
    }

    public function build_tile_xml($tile_nav_uri, $wide2, $wide3){
        $msg =  "<?xml version=\"1.0\" encoding=\"utf-8\"?>" .
                "<wp:Notification xmlns:wp=\"WPNotification\" Version=\"2.0\">" .
                "<wp:Tile Id=\"/Route.xaml?scheduleId=$tile_nav_uri\" Template=\"IconicTile\">" .
                //"<wp:SmallIconImage>$image_url_small</wp:SmallIconImage>" .
                //"<wp:IconImage>$image_url_large</wp:IconImage>" .
                //"<wp:WideContent1>$wide1</wp:WideContent1>" .
                "<wp:WideContent2>$wide2</wp:WideContent2>" .
                "<wp:WideContent3>$wide3</wp:WideContent3>" .
                //"<wp:Count>$count</wp:Count>" .
                //"<wp:Title>$title</wp:Title>" .
                //"<wp:BackgroundColor>#00FFFFFF</wp:BackgroundColor>" .
                "</wp:Tile>" .
                "</wp:Notification>";

        return $msg;

    }

    public function post_tile($uri, $xml_data, $type = WPNTypesEnum::Tile, $tileTag = '', $count = 0){
        if($this->access_token == ''){
            $this->get_access_token();
        }

        $headers = array('Content-Type: text/xml', "Content-Length: " . strlen($xml_data), "X-WNS-Type: $type", "Authorization: Bearer $this->access_token");
        if($tileTag != ''){
            array_push($headers, "X-WNS-Tag: $tileTag");
        }

        $ch = curl_init($uri);
        # Tiles: http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh868263.aspx
        # http://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx
        curl_setopt($ch, CURLOPT_CAPATH, "/var/www/clients/client1/web1/ssl/");
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POSTFIELDS, "$xml_data");
        curl_setopt($ch, CURLOPT_VERBOSE, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $response = curl_getinfo( $ch );
        curl_close($ch);

        $code = $response['http_code'];
        if($code == 200){
            return new WPNResponse('Successfully sent message', $code);
        }
        else if($code == 401){
            $my_file = '/var/log/WPNotify.txt';
            $handle = fopen($my_file, 'a') or die('Cannot open file:  '.$my_file);
            $data = date("d-M-y H:i:s:") . "401 Authorisation Error:" . $this->access_token . "\n";
            fwrite($handle, $data);

            if($count == 10) {
                exit;
            }
            $this->access_token = '';
            return $this->post_tile($uri, $xml_data, $type, $tileTag, $count++);
        }
        else if($code == 410 || $code == 404){
            return new WPNResponse('Expired or invalid URI', $code, true);
        }
        else{
            return new WPNResponse('Unknown error while sending message', $code, true);
        }
    }
}
?>

There could be an error in my code, or it could be that I'm not setting up the backend stuff correctly (most of the MSDN docs are aimed at Windows Servers and .aspx, so I've tried to replicate those instructions but could have gone wrong somewhere along the line!).

If anyone can help in either fixing errors in the code, or with the client authentication for the SSL certificate it would be much appreciated!

Thanks, Jake

crazyloonybin
  • 935
  • 3
  • 18
  • 36
  • [Look up HTTP Error 401](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html), its fairly self explanitory and possibly not even related to the acres of code you expect us to wade through. – RiggsFolly Jul 30 '14 at 13:23
  • 1
    @RiggsFolly I know what a 401 error is, just after spending the past 3 days trying to fix this problem to no avail, I hoped someone on here would be able to shed some light on the situation. I realise there is a lot of code there, but if I had asked the question without any code, I guarantee the first comment would have been 'Post your code' – crazyloonybin Jul 30 '14 at 13:29

1 Answers1

0

take a look at Windows Notifications Service: 401 Invalid Token when trying to create a Toast notification in PHP invalid-token-when-trying-to-create-a-toast-n

make sure you're sending package SID of your app as the 'client_id' parameter instead of your app's client_id when requesting access token.

This is really confusing ,doing both are able to get access_token from Microsoft's server, but only the token requested with you package SID won't give you 401 error when sending toast notification.

Community
  • 1
  • 1
Hao-Cher Hong
  • 230
  • 2
  • 6