Build Your Skype Bot with OAuth and REST


This page will be redirected to the "BUILD BOT with Microsoft Bot Framework Rest Api" after 10 seconds.
Because the Skype Bot development has moved to the new Microsoft Bot Framework 3.

As you know, you can use the .NET (C#) and node.js SDKs for building your Skype bot. But for other programming languages like PHP, Ruby, Python with scientific libraries etc, you must handle the HTTP and OAuth directly.
In this blog post I explain about this HTTP flow (incl. AuthN) of Skype Bot without SDK. Using this flow, you can develop Skype Bot with any programming language.

Beforehand, you must register your bot (here), and please see the tutorial document. (In this blog post we assume that this registration is done.)

Basic flow of calling

The following picture illustrates the calling flow in the Skype Bot Platform.
This platform (Skype Bot Platform) provides the basic bot infrastructures (Bot Directory, etc) fronting on end-users, and your code is called through this platform in the backend.
That is, your code (your bot) must communicate with this Skype Bot Platform.

If you are using Microsoft Bot Framework with Skype, eventually the Microsoft Bot Framework connects to this platform. See the previous post of "HTTP Flow of Microsoft Bot Framework" for details.

Authentication (outgoing - your code to skype)

Before starting messaging communications, you must learn about the authentication for secure communications.

The message from your bot must be protected by Azure AD v2 endpoint, otherwise the malicious code might call the Skype Bot Platform instead of your bot.
In this section, I explain about how to accomplish this flow.

Skype Bot uses the app-only access token in Azure AD v2 endpoint. To get this kind of access token, you just send the HTTP request as follows. (Beforehand, you must retrieve the following client_id and client_secret from the app registration portal. These are the bot's client id and secret.)

POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=761b6e57-2dc1-4de9-b5d1-9599a9902117&client_secret=3ayhQ55FqU...&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default

As a result, you can receive the following HTTP response, and this includes the following access token. (Notice : This expires in 1 hour.)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "ext_expires_in": 3600,
  "access_token": "eyJ0eXAiOi..."
}

Please hold this access token in your bot application, because you must always set this access token as Authorization header for your outgoing messages, and the Skype Bot Platform verifies this token for checking if the valid code is sending.

Notice : This access token also includes the claims (client id, etc) for communications, and the Skype Bot Platform can identify your bot using this access token. (Please see the previous post of "How to verify the OAuth token with the v2.0 endpoint" for retrieving this claim information.)

POST https://apis.skype.com/v2/conversations/8:live:tsuyoshi.matsuzaki/activities/
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json

{"message":{"content":"How are you ?"}}

Note : See "How to use Application Permission with v2 endpoint and Microsoft Graph" for the details about this Azure AD v2.0 endpoint client credential flow.

Authentication (incoming - skype to your code)

The message from Skype Bot Platform is also protected by Authorization header. (see the following header in bold fonts.) In this case, your bot must verify the message for secure communications by yourself. (If not, your code might be called by the malicious code.)

POST https://example.com/yourbot
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json; charset=utf-8

[
  {
    "action": "add",
    "activity": "contactRelationUpdate",
    "from": "8:live:tsuyoshi.matsuzaki",
    "fromDisplayName": "Tsuyoshi Matsuzaki",
    "to": "28:761b6e57-2dc1-4de9-b5d1-9599a9902117",
    "time": "2016-07-11T06:30:09.512Z"
  }
]

In this case, the Azure AD is not used. The key in Skype Bot Platform is used for this authentication (AuthN and AuthZ).
First, you must retrieve the OpenID / OAuth configuration information of the Skype Bot Platform hosted at https://api.aps.skype.com/v1/.well-known/openidconfiguration. It returns as follows. (Notice : This might change in the future, so don't copy this json value in your production code.)

{
  "issuer": "https://api.botframework.com",
  "authorization_endpoint": "https://anonymous.invalid.com",
  "jwks_uri": "https://api.aps.skype.com/v1/keys",
  "id_token_signing_alg_values_supported": [
    "RSA256"
  ],
  "token_endpoint_auth_methods_supported": [
    "private_key_jwt"
  ]
}

The public key (X.509 certificate) is stored in the previous "jwks_uri" location. Then you must retrieve these key information and you verify the previous "Authorization" header (access token) using this key.

As I explained in the previous post of "How to verify the OAuth token with the v2.0 endpoint", this verification is accomplished by the simple steps.
Here is the PHP sample code for this verification.

<?php
// Authorization header value
$token = "eyJ0eXAiOi...";
// 0:Invalid, 1:Valid
$token_valid = 0;
  
// 1 separate token by dot (.)
$token_arr = explode('.', $token);
$headers_enc = $token_arr[0];
$claims_enc = $token_arr[1];
$sig_enc = $token_arr[2];

// 2 base 64 url decoding
$headers_arr = json_decode(base64_url_decode($headers_enc), TRUE);
$claims_arr = json_decode(base64_url_decode($claims_enc), TRUE);
$sig = base64_url_decode($sig_enc);

// 3 get key list
$keylist = file_get_contents('https://api.aps.skype.com/v1/keys');
$keylist_arr = json_decode($keylist, TRUE);
foreach($keylist_arr['keys'] as $key => $value) {
  
  // 4 select one key (which matches)
  if($value['kid'] == $headers_arr['kid']) {
  
    // 5 get public key from key info
    $cert_txt = '-----BEGIN CERTIFICATE-----' . "\n" . chunk_split($value['x5c'][0], 64) . '-----END CERTIFICATE-----';
    $cert_obj = openssl_x509_read($cert_txt);
    $pkey_obj = openssl_pkey_get_public($cert_obj);
    $pkey_arr = openssl_pkey_get_details($pkey_obj);
    $pkey_txt = $pkey_arr['key'];
    
    // 6 verify signature
    $token_valid = openssl_verify($headers_enc . '.' . $claims_enc, $sig, $pkey_txt, OPENSSL_ALGO_SHA256);
  }
}

// 7 show result
if($token_valid == 1)
  echo 'Token is Valid';
else
  echo 'Token is Invalid';

// Helper functions
function base64_url_decode($arg) {
  $res = $arg;
  $res = str_replace('-', '+', $res);
  $res = str_replace('_', '/', $res);
  switch (strlen($res) % 4) {
    case 0:
    break;
    case 2:
    $res .= "==";
    break;
    case 3:
    $res .= "=";
    break;
    default:
    break;
  }
  $res = base64_decode($res);
  return $res;
}
?>

HTTP flow - Adding your bot

The authentication flow is all done ! All you have to do is to communicate using HTTP (REST-styled messaging) with the Skype Bot Platform.

First, if your bot is added (subscribed) by a Skype user (customer), the following HTTP webhook arrives to your bot.
As I explained in the above, you must check the Authorization header value and proceed your actions.

POST https://example.com/yourbot
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json; charset=utf-8

[
  {
    "action": "add",
    "activity": "contactRelationUpdate",
    "from": "8:live:tsuyoshi.matsuzaki",
    "fromDisplayName": "Tsuyoshi Matsuzaki",
    "to": "28:761b6e57-2dc1-4de9-b5d1-9599a9902117",
    "time": "2016-07-11T06:30:09.512Z"
  }
]

The "action" attribute means what kind of bot action is published. In this case, this means that the user added your bot.
The "from" is the Skype user id, and the "to" is your bot id. Your bot must store this "from" id in your database, because your bot can communicate with your bot's users (the bot's subscribers) using this id.

Notice : The number of "8" means the skype user, and "28" means the skype bot.

If your bot accepts this request, you just response HTTP status 201.

HTTP/1.1 201 Created

When your bot is removed from the contact list, the following HTTP request (webhook) is received. (In this case you just also response the HTTP status 201.)

POST https://example.com/yourbot
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json; charset=utf-8

[
  {
    "action": "remove",
    "activity": "contactRelationUpdate",
    "from": "8:live:tsuyoshi.matsuzaki",
    "fromDisplayName": "Tsuyoshi Matsuzaki",
    "to": "28:761b6e57-2dc1-4de9-b5d1-9599a9902117",
    "time": "2016-07-12T09:52:36.574Z"
  }
]

HTTP flow - Incoming message

If the Skype user sends the message "Good morning !" to your bot, the following HTTP webhook arrives.

POST https://example.com/yourbot
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json; charset=utf-8

[
  {
    "id": "0",
    "content": "Good morning !",
    "activity": "message",
    "from": "8:live:tsuyoshi.matsuzaki",
    "fromDisplayName": "Tsuyoshi Matsuzaki",
    "to": "28:761b6e57-2dc1-4de9-b5d1-9599a9902117",
    "time": "2016-07-12T06:26:08.231Z"
  }
]

This incoming message is very similar to the privious one, then I think there's no need to explain about details. But there's some notation for this incoming messages.

The "id" is called "activity id". Sometimes this id is refered by other communication.
For example, when this incoming message is for attachment (images, etc), you can ask for the binary data (which is encoded by base64) using this id.
When not refered, this id is "0".

As you notice, the message body uses the json array. When there are multiple messages, these can be included in one request using the json array.

If your bot accepts this request, you just response HTTP status 201.

HTTP/1.1 201 Created

HTTP flow - Outgoing message

On the other hand, when your code send the outgoing message (which is the message from your bot to the Skype user), you send the following HTTP request to the Skype Bot Platform.
Each HTTP request is one-way (not request-reply), and it can be bidirectional (incoming and outgoing). Your code can also call the Skype user using one-way messaging like timer bot or some notification bot.  (There's no need to use request-reply style messaging.)

POST https://apis.skype.com/v2/conversations/8:live:tsuyoshi.matsuzaki/activities/
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json

{"message":{"content":"Good morning !"}}

The "8:live:tsuyoshi.matsuzaki" in the uri fragment is the conversation id. When your bot is sending the message to one Skype user, this id can be Skype Id itself. When group conversation, conversation thread id will be used.

If the Skype Bot Platform accepts this incoming message, HTTP status 201 is returned. (As I explained, the Authorization header is checked by the platform.)

HTTP/1.1 201 Created

You can also use emoji, attachment, and so on. Next is the emoji example.

POST https://apis.skype.com/v2/conversations/8:live:tsuyoshi.matsuzaki/activities/
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json

{"message":{"content":"<ss type =\"wink\">;)</ss>"}}

 

In this blog post I've just explained the basic concepts and steps building Skype Bot, but Skype Bot can handle more advanced scenarios like the attachment, group chat, and audio/video calling, etc. (The audio is the limited preview, and the video will be in the future release.)

Don't worry about the supported programming language in SDK, and please try !

 

Comments (17)

  1. sina bayat says:

    great thanks 🙂
    ارم

  2. Puneet says:

    Do I really need to fetch access_token after every hour?

    1. Yes.
      In the usual OAuth flow (code flow, etc), you get the “refresh_token” in HTTP response by adding “offline_access” in scope, and you retrieve the new “access_token” using this “refresh_token” silently. i.e, you must always fetch the access_token by every hour using Azure AD. (Though it’s the Azure AD v1 endpoint, Vittorio is writing about the token lifetime in http://www.cloudidentity.com/blog/2015/03/20/azure-ad-token-lifetime/)
      This time, no need to use “refresh_token”, because this flow itself can retrieve the new “access_token” silently. (If the access_token is expired, the specific error is returned and you can call this flow repeatedly every hour.)

      1. Puneet Singh says:

        thanx I am using rest api in python and I created a daemon thread in bg which refreshes token after every 50 min. Next after reading your article I am also planning to add jwt security. 😊

  3. Hillel Coren says:

    Thanks for the extremely helpful article!

    I’m trying to use attachments and track state, most URLs require a conversationId. Where can I find it, does is it need to be generated with the CreateConversation API command?

    1. Hillel Coren says:

      To clarify, I keep getting 404 errors with the conversation id I’m using.

      I think it may be related to differences between v2 and v3.

    2. Hi, Hillel-san. I’m sorry, but Skype Bot development has been moving into the Microsoft Bot Framework, then I will write again about the flow using Microsoft Bot Framework 3.x.
      I apologize, I’ll take a little bit long vacation (called “obon” in Japan), and I’ll write a little bit later. Thanks

  4. jose says:

    how can i get the info of my bot by using access token generated through App ID and secret?

    1. Hi Jose-san. Could you please tell me what kind of info (“the info of my bot”) you’re meaning ? bot id ?

      1. jose says:

        hi Tsuyoshi Matsuzaki,,, i mean the info of my bot using the access token … is there any way to get that?

        1. In the claim of access token, there are several information about your client (i.e, bot), but they are no useful information. If you use your bot related info (for example, your bot name, your bot id, etc), you must remember in your bot program.
          Of course, if you want to use the information for each user (or conversation) in your bot, you can use “bot state” in the Microsoft Bot Framework. (see https://blogs.msdn.microsoft.com/tsmatsuz/2016/07/12/developing-skype-bot/)

          1. jose says:

            is there any way to get my bot id using token ?

        2. The app id of the bot is included in the JWT token, but bot id is not included. In usual way, you must set your app id and bot id into your bot app (server side code) beforehand.

  5. jijo says:

    Is there any API calls with which we can get the skype bot id, bot name using the access token

  6. jijo says:

    how the skype bot access token can be refreshed

    1. The result is not including refresh token, then you retrieve access token again if it’s expired. (No login UI is needed for this app permission’s flow.)

  7. Mahesh Kanniah says:

    I keep getting 403 errors whether i call it from a php file or from my node script.
    Endpoint is https://skype.botframework.com/v3/conversations/29%3A1sVK632j4iGqA2iMb3QKHhwBu1B86PoznjvXCsghPFbM/activities/1482336067364

    I have been able to generate a token and verified the headers as well. Everything seems fine.
    But yet the 403 error keeps on coming

    $message = ‘{
    “type”: “message/text”,
    “text”: “Hi! (wave)”
    }’;

    $ch = curl_init($url);

    $headr = array();
    $headr[] = ‘Content-length: ‘ . strlen($message);
    $headr[] = ‘Content-type: application/json; charset=utf-8’;
    $headr[] = ‘Authorization: Bearer ‘.$token;

    $options = array(
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYHOST => false,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_HTTPHEADER => $headr,
    CURLOPT_POSTFIELDS => $message,
    CURLOPT_POST => true,
    CURLOPT_URL => $url ,
    ;
    $response = curl_exec($ch);

    curl_setopt_array($ch , $options);

    1. It seems that you’re using bot framework endpoint (when skype, https://skype.botframework.com/), not skype bot platform endpoint (https://apis.skype.com). If you use bot framework, could you please follow the next post. (This post is writing for skype bot platform.)
      https://blogs.msdn.microsoft.com/tsmatsuz/2016/08/19/build-skype-bot-with-microsoft-bot-framework-oauth-and-rest-api/
      If the error exists, could you please send me the access token string ? This error seems to be caused by the token.

Skip to main content