Search code examples
angularscalatwitterakkatwitter-oauth

Twitter Sign In - Redirecting User to Authentication


I'm having trouble authenticating the user to Twitter. I've developed a Scala Code where I handle all the server-side connection to Twitter, and an Angular 5+ app as my frontend. So, when the user clicks on the HTML-Twitter Button, this Angular function activates:

callTwLogin(): void{
    // StepOne
    var win = window.open('','_blank','height=500,width=800'); // I open a blank popup window 
    const callback2 = 'http://127.0.0.1:8081/TwitterRedirect'; 
    this.http.get(url1+callback2, {responseType: 'text'}).subscribe(data =>{
      console.log(data); //Just for testing purposes. 
    });
   }

Now, my Scala/Akka server-side code handles this GET request and gets the request_token from Twitter. Then, it obtains the request_token and the token_secret and does a GET request to oauth/authenticate, in order to get the HTML.

val route = cors(settings){
      path("requestToken"){
        get {
          parameters('callback.as[String])(cb => {
            val callback = this.encodeUriComp(cb)
            val url = "https://api.twitter.com/oauth/request_token"
            this.oauth_timestamp = this.createTimestamp()
            this.oauth_nonce = this.randomString(32)
            val authorization = headers.RawHeader("Authorization",
              """OAuth oauth_callback="""" + callback +
                """", oauth_consumer_key="""" + this.consumerKey +
                """", oauth_nonce="""" + this.oauth_nonce +
                """", oauth_signature="""" + this.encodeUriComp(this.createSignature(callback)) +
                """", oauth_signature_method="HMAC-SHA1", oauth_timestamp="""" + this.oauth_timestamp +
                """", oauth_version="1.0"""")
            val params = ByteString(callback)
            val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(HttpMethods.POST, url,
              headers = List(authorization),
              entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, params)))

            implicit val timeout = Timeout(5, TimeUnit.SECONDS)
            try {
              val result = Await.result(responseFuture, timeout.duration)
              val uri = "http://localhost:4200"
              val Str = result.entity.httpEntity.toString()
              val response = Str.substring(Str.indexOf("o"),Str.indexOf(")")) //This is the final response.
              println(response)
              val arr = setRequestTokenRSP(response)
              this.oauth_token = arr(0)
              this.oauth_token_secret = arr(1)
              if(arr(2)=="true"){ // if oauth_callback_confirmed == true
                val uri = "https://api.twitter.com/oauth/authenticate?oauth_token="+arr(0)
                val rspFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(HttpMethods.GET, uri))
                implicit val to = Timeout(5, TimeUnit.SECONDS)
                try{
                  val res = Await.result(rspFuture,to.duration)
                  complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,res.entity.dataBytes))
                }
                catch{
                  case e: TimeoutException => complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`,"Couldn't access authentication"))
                }
               // complete(HttpResponse(status = StatusCodes.PermanentRedirect,headers = headers.Location(uri) :: Nil))
              }
              else complete("Error recieving Request Token")
              //complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`,result.entity.dataBytes))
            }
            catch{
              case e: TimeoutException => complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`,"Couldn't process request"))
            }
          })
        }
      }

Now this is where it gets tricky. I get the whole HTML but, when passing it to my frontend, it won't recognize the Twitter session. So I would need to Log in with my Twitter user/pass everytime I do this step.

In order to avoid this, I tried to do a straight HTTP 302 redirect to /oauth/authenticate so my frontend would navigate to Twitter Authentication Page. Of course, that didn't help, because of CORS restriction.

What should I do to make my user visit the authentication page in a seamlessly way?


Solution

  • I did it ! ... The most seamlessly way to accomplish this was to return the URL to my Angular frontend app. Then, I will simply redirect the popUp to the URL with window.location.href and create an EventListener that waits for a message sent by my server.

    This is my web app code:

    callTwLogin(): void{
        // StepOne
        var win = window.open('','_blank','height=500,width=800'); //Open a Blank PopUp.
        /** Add a listener so Scala can send a PostMessage when completing Authorization */
        window.addEventListener("message", recieveMessage, false);
        function recieveMessage(event){
          if (~event.origin.indexOf("http://127.0.0.1:8081")) {
            console.log(event.data);
          }
          else console.log("Error");
        }
        /** Making a call to backend, specifying callback URL */
        const url = 'http://127.0.0.1:8081/requestToken?callback=';
        const callback = 'http://127.0.0.1:8081/TwitterRedirect';
        this.http.get(url+callback, {responseType: 'text'}).subscribe(data =>{
          console.log(data);
          win.location.replace(data);   
        });
       }
    

    And my server code will consist in 2 routes. The second one will handle the oauth_verifier and make a PostMessage to my web app.

    path("requestToken"){
            get {
              parameters('callback.as[String])(cb => {
                /**
                  * I NEED TO CHANGE AWAIT TO ONCOMPLETE!!!!
                  */
                  val callback = this.encodeUriComp(cb)
                  val url = "https://api.twitter.com/oauth/request_token"
                  this.oauth_timestamp = this.createTimestamp()
                  this.oauth_nonce = this.randomString(32)
                  val authorization = headers.RawHeader("Authorization",
                    """OAuth oauth_callback="""" + callback +
                      """", oauth_consumer_key="""" + this.consumerKey +
                      """", oauth_nonce="""" + this.oauth_nonce +
                      """", oauth_signature="""" + this.encodeUriComp(this.createSignature(callback)) +
                      """", oauth_signature_method="HMAC-SHA1", oauth_timestamp="""" + this.oauth_timestamp +
                      """", oauth_version="1.0"""")
                  val params = ByteString(callback)
                  val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(HttpMethods.POST, url,
                    headers = List(authorization),
                    entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, params)))
    
                  implicit val timeout = Timeout(5, TimeUnit.SECONDS)
                  try {
                    val result = Await.result(responseFuture, timeout.duration)
                    val uri = "http://localhost:4200"
                    val Str = result.entity.httpEntity.toString()
                    val response = Str.substring(Str.indexOf("o"), Str.indexOf(")")) //This is the final response.
                    println(response)
                    val arr = setRequestTokenRSP(response)
                    this.oauth_token = arr(0)
                    this.oauth_token_secret = arr(1)
                    if (arr(2) == "true") { // if oauth_callback_confirmed == true
                      val uri = "https://api.twitter.com/oauth/authenticate?oauth_token=" + arr(0)
                      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, uri))
                    }
                    else complete("Error recieving Request Token")
                    //complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`,result.entity.dataBytes))
                  }
                  catch {
                    case e: TimeoutException => complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Couldn't process request"))
                  }
                })
            }
    
          }~
          path("TwitterRedirect"){
            get{
              parameters('oauth_verifier.as[String], 'oauth_token.as[String])((verifier, token) => {
                val url = "http://127.0.0.1:8081/getVerifier?verifier="+verifier
                if(this.oauth_token==token) { // Check whether token recieved in authorization equals the previous one.
                  this.oauth_verifier = verifier
                  val rspToAngular = verifier + "|" + token
                  val html =
                    "<!doctype html>" +
                      "<html lang='en'>" +
                      "<head>" +
                      "<meta charset='UTF-8'> <title>Popup</title> </head>" +
                      "<body> <script type='text/javascript'>" +
                      "window.opener.postMessage('" + rspToAngular + "', 'http://localhost:4200');" +
                      "</script></body></html>"
                  complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, html))
                }
                else {
                  val html =
                    "<!doctype html>" +
                      "<html lang='en'>" +
                      "<head>" +
                      "<meta charset='UTF-8'> <title>Popup</title> </head>" +
                      "<body> <script type='text/javascript'>" +
                      "window.opener.postMessage('Error: Token Mismatch', 'http://localhost:4200');" +
                      "</script></body></html>"
                  complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,html))
                }
              })
            }
          }