Part 2: Connecting to a Comm100 account
  • 20 Jun 2022
  • 6 Minutes to read
  • Dark
    Light

Part 2: Connecting to a Comm100 account

  • Dark
    Light

Article Summary

Introduction

In our Vincall program, we use the OAuth2 method to connect to Comm100. OAuth2 is a standard of open authorization, allowing users to authorize the B application to access some specific resources from the A application server without providing an account or password. In this tutorial, we use the authorization code mode of OAuth2.
The sequence diagram is shown as follow.

image.png

Vincall backend callback service is the interface that receive Comm100 OAuth redirection and get the access token. Comm100 OAuth is the authorization system providing the access token and agent infomation interfaces.

The interface of Connection Comm100 in Vincall interface is shown as follow.

image.png

If connection succeeds, the web interface will show the Vincall agent and Comm100 user mapping interface.

image.png

Step-by-Step Instructions

We will teach you to follow the flowchart to implement this process step by step. We will implement this process from the frontend, backend and database. Here are the steps.

  1. Creating OAuth Client in Comm100 Site
  2. Adding Tab and Button in Frontend
  3. Logging in Comm100 and Redirecting to Vincall Backend Callback Service
  4. Requesting Token and Redirecting to the Vincall Frontend
  5. Checking Parameter Success and Displaying the Mapping Page

Creating OAuth Client in Comm100 Site

  1. Create a new OAuth Client in the Comm100 Control Panel.

image.png

  1. Enter the following required information, where the clientId is a user-defined string and the redirect_uri is the callback interface address.

image.png

The OAuth Client information in vincall is shown as follow.

image.png

  1. Store ClientId and ClientSecrect in the database, and add parameters grant_type, redirect_uri, redirect_logon, and domain (which will be used later).

image.png

grant_type is the authorization_code which represents authorization code pattern.
redirect_uri is the address of the callback interface of OAuth defined in vincall, the address of the vincall backend callback service interface, which is https://api.vincall.net/api/sso/connectcallback.
redirect_logon is the vincall frontend address of the final redirect, which is https://vincall.net/oauth.
Domain is the host of comm100OAuth, which is https://voipdash.comm100.io.

Adding Tab and Button in Frontend

In the Vincall frontend interface, you need to set a Connect Comm100 menu bar option. Then add the siteId input box and the CONNECT COMM100 button.
The frondend codes below are in vincall-portal project.

Connect Comm100 menu bar options javascript(src/App.tsx).

  <Route path="/connect" element={<ConnectPage />} />

Define the siteId input box and the Connect Comm100 button(src/Pages/Connect/ConnectComm100.tsx).

  export const ConnectComm100 = ({
  connected,
  setConnected,
  triggerPageRefresh,
  connectInfo,
}: ConnectComm100Props) => {
  const { handleConnect, handleDisconnect, ref } = connectComm100App({
    setConnected,
    triggerPageRefresh,
    connectInfo,
  });

  return (
    <div style={{ height: 150 }}>
      {connected ? (
        <>
          <Typography>You are already connected.</Typography>
          <Typography>
            Connected Site ID:{" "}
            {localStorage.getItem("connectSiteId") || ref.current}
          </Typography>
          <Button variant="contained" onClick={handleDisconnect}>
            Disconnect Comm100
          </Button>
        </>
      ) : (
        <>
          <Typography>
            You must connect to Comm100 to get account mappings, Please click
            the button below.
          </Typography>
          <div>
            <TextField
              label="Site ID"
              variant="outlined"
              onChange={(e: any) => (ref.current = e.target.value)}
            />
          </div>
          <div>
            <Button variant="contained" onClick={handleConnect}>
              Connect Comm100
            </Button>
          </div>
        </>
      )}
    </div>
  );
};

Logging in Comm100 and Redirecting to Vincall Backend Callback Service

When the user enters siteId in the Vincall interface and clicks Connect, the login box of Comm100 pops up. The user enters the account and password to log in to Comm100. After logging in, the Comm100 backend redirects to the Vincall backend callback service with the authorization code.The frondend codes below are in vincall-portal project.

If user clicks the Connect Comm100 button, it will redirect to the Comm100 login interface.(src/Pages/Connect/Application/ConnectComm100App.tsx)

  export const connectComm100App = ({
  setConnected,
  triggerPageRefresh,
  connectInfo,
}: ConnectComm100AppProps): ConnectComm100ButtonApp => {
  const ref = useRef();
  ...//define function handleConnect and handleDisConnect, the code is below
  return {
    handleConnect,
    handleDisconnect,
    ref,
  };
};

  const handleConnect = () => {
    const siteId = ref.current;
    localStorage.setItem("connectSiteId", siteId || "");
    const redirect_url = `${EnvConfig.serverUrl}/sso/connectcallback?siteId=${siteId}&domain=${connectInfo.domain}`;
    const url = `${
      EnvConfig.routeUrl
    }/oauth/authorize?siteId=${siteId}&client_id=${
      connectInfo.clientId
    }&redirect_uri=${encodeURIComponent(redirect_url)}&response_type=code`;
    // @ts-ignore
    window.__refreshComm100Connect = triggerPageRefresh;
    const win = window.open(
      url,
      "ConnectPage",
      `
        width = 500,
        height = 678,
        left= ${window.innerWidth / 2 - 250},
        top = ${window.innerHeight / 2 - 339},
        menubar = false,
        toolbar = false,
        location = false,
        resizable = true,
        scrollbars = true
      `
    );
  };

If logging in succeeds, Comm100 window is as follow:

image.png

image.png

If logging in fails, return error:

image.png

If user has already logged in, the button will be Disconnect(src/Pages/Connect/Application/ConnectComm100App.tsx). If click it, it will call the Vincall API to disconnect.

  const handleDisconnect = () => {
    customHttpClient(
      `${getServerURL()}/connectState/disconnect?siteId=${localStorage.getItem(
        "connectSiteId"
      )}`,
      {
        method: "PUT",
      }
    ).then((res: any) => setConnected(res.json.connected));
  };

Requesting Token and Redirecting to the Vincall Frontend

In the Vincall backend callback service, the authorization code and the OAuth Client information saved in the database in advance are used to request access_token from Comm100 OAuth. After obtaining the access_token, it will be saved to the database, and it will redirect to the frontend of the Vincall with the parameter "success" as true. We need to implement the interface for handling authorization callbacks(api/sso/connectcallback) in vincall-service project.

  1. Get the OAuth Client information from the database(vincall.service/vincall.service/Controllers/SSoController). These information can be obtained from VoIP site. We have already store them in the database in advance. Then redirect to Comm100 OAuth to obtain the access_token.
   var oauthInfo=await _settingService.GetAsync("vincallconnect");// get auth information from database
   var queryString= HttpContext.Request.QueryString.Value;
   var originalUrl=queryString.Substring(0,queryString.IndexOf("&code="));
   _hostProvider.Host=model.Domain ?? oauthInfo.domain;// set the host url for the Comm100 auth API
   var tokenResult=await _comm100authClient.QueryAccessTokenAsync(model.SiteId, model.Code, oauthInfo.client_id,    oauthInfo.client_secret,oauthInfo.grant_type, oauthInfo.redirect_uri + originalUrl);//send post request with webAPIClientCore, the host uri is '_hostProvider.Host',and the path is '/oauth/token'
  1. With the access_token, we can call the userInfo API of Comm100 OAuth to obtain user information(vincall.service/vincall.service/Controllers/SSoController). More details, click [Comm100 oauth API] (https://dev.azure.com/Comm100/Main/_wiki/wikis/Comm100%20RandD.wiki/1717/OAuth) for detailed API information.
   var info=await _comm100authClient.GetProfileInfoAsync($"{tokenResult.token_type} {tokenResult.access_token}",model.SiteId);
  1. Check the user information. If the agentId in user information is empty, redirect to the Vincall frontend with parameter success false.
   if(string.IsNullOrEmpty(info.AgentId)){
       var uriRedirect= BuildUrl(returnUri, new Dictionary<string, StringValues>{{"errMsg","unauthorization"},{"success","false"}});
       return Redirect(uriRedirect.ToString());
   }
  
  1. If the agentId in user information is not empty, save the access_token to the database and redirect to the Vincall frontend with parameter success true(vincall.service/vincall.service/Controllers/SSoController).
   if(´╝üstring.IsNullOrEmpty(info.AgentId)){
       var uriRedirect= BuildUrl(returnUri, new Dictionary<string, StringValues>{{"access_token",tokenResult.access_token},{"success","true"}});
       await _settingService.SaveConnectTokenAsync(Convert.ToInt32(info.SiteId), tokenResult.access_token, tokenResult.refresh_token);//save token into the database
       return Redirect(uriRedirect.ToString());
   }
  1. The complete code
  [HttpGet]

  public async Task<IActionResult> ConnectionCallbackAsync([FromQuery]SsoCallModel model)
  {
      var oauthInfo=await _settingService.GetAsync("vincallconnect");// get auth information from database
      var originalUrl=HttpContext.Request.QueryString.Value.Substring(0,queryString.IndexOf("&code="));
      _hostProvider.Host=model.Domain ?? oauthInfo.domain;// set the host url for the Comm100 auth API
      
      // Comm100 oauth API params
      var siteId=model.SiteId;
      var code=model.Code;
      var clientId=oauthInfo.client_id;
      var clientSecrect=oauthInfo.client_secret;
      var grantType=authInfo.grant_type;
      var redirectUri=oauthInfo.redirect_uri + originalUrl;
      var tokenResult=await _comm100oauthClient.QueryAccessTokenAsync(siteId, code, clientId,clientSecret,grantType, redirectUri);//send post request with webAPIClientCore, the host uri is '_hostProvider.Host',and the path is '/oauth/token'
   }
      // get userInformation with access_token
      var info=await _comm100oauthClient.GetProfileInfoAsync($"{tokenResult.token_type} {tokenResult.access_token}", siteId);
      // redirect to frontend
      if(string.IsNullOrEmpty(info.AgentId))
      {
         var uriRedirect= BuildUrl(returnUri, new Dictionary<string, StringValues>{{"errMsg","unauthorization"},{"success","false"}});
         return Redirect(uriRedirect.ToString());
      }
      else
      {
         var uriRedirect= BuildUrl(returnUri, new Dictionary<string, StringValues>{{"access_token",tokenResult.access_token},{"success","true"}});
         await _settingService.SaveConnectTokenAsync(Convert.ToInt32(info.SiteId), tokenResult.access_token, tokenResult.refresh_token);//save token into the database
         return Redirect(uriRedirect.ToString());
      }
   }


   public interface IComm100oauthClient : IHttpAPI
   {
     [HttpPost("/oauth/token")]
     [HostFilterAttribute]
     Task<TokenResult> QueryAccessTokenAsync([PathQuery]string siteId, [FormContent]string code, [FormContent]string client_id, [FormContent]string client_secrect, [FormContent]string grant_type, [FormContent]string redirect_uri);

     [HttpGet("/oauth/userinfo")]
     [HostFilterAttribute]
     Task<Comm100Info> GetProfileInfoAsync([Header("Authorization")]string token, [PathQuery]string siteId);
    }


  public class HostFilterAttribute: ApiFilterAttribute
  {

    public override Task OnResponseAsync(ApiResponseContext context)
    {
       return Task.CompletedTask;
    }

    public override Task OnRequestAsync(ApiRequestContext context)
    {
       var sp=context.HttpContext.ServiceProvider;
       var hostProvider=sp.GetRequiredService<HostProvider>();
       var requestMessage requestMessage= context.HttpContext.RequestMessage;
       requestMessage.RequestUri=requestMessage.MakeRequestUri(new Uri(hostProvider.Host));
       return Task.CompletedTask;
    }

  }

  public class HostProvider
  {
     public string Host { get; set; }
  }

  public class Comm100Info
  {
     public string AgentId { get; set; }
     public string SiteId { get; set; }
     public string PartnerId { get; set; }
     public string Email { get; set; }
  }

  public class SsoCallModel
  {
     public string Code { get; set; }
     public string SiteId { get; set; }
     public string Domain { get; set; }
     public string ReturnUri { get; set; }
  }
  

Startup.cs
   
   services.AddScoped<HostProvider>();

Checking Parameter Success and Displaying the Mapping Page

If the parameter success returned by the backend is true, display the Vincall agent and Comm100 user mapping interface. For more details, click Mapping the Agents.

Next Steps

This article teaches you how to connect to Comm100 account via OAuth2. After you get the Comm100 access token, you can access Comm100 APIs. For example, in the Vincall we call the Comm100 agent API to implement the agent mapping function with Comm100 access token. For more details, click Mapping the Agents.


Was this article helpful?