DEV Community

Gbubemi
Gbubemi

Posted on

Twitter Sign In with .net core 3.1 and Angular

Alt Text

Implementations of Sign in with twitter are based on OAuth.

Working with OAuth 1.0a can get really clumsy and you have to get the signature handling right. I found a really nice and easy to implement OAuth library for dotnet https://github.com/rhargreaves/oauth-dotnetcore.

GETTING STARTED

First, you have to apply for a developer account on https://developer.twitter.com/ and register your app. You will then get a consumer key and consumer secret. In this post I would complete the sign in with twitter in 3 steps.

STEP 1:Obtaining a request token

To obtain a request token we need to make a post request to https://api.twitter.com/oauth/request_token. The body of the successful response will contain the oauth_token, oauth_token_secret, and oauth_callback_confirmed parameters.

Create a model for the request token response.

public class RequestTokenResponse
{
    public string oauth_token { get; set; }
    public string oauth_token_secret { get; set; }
    public string oauth_callback_confirmed { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

I would be using dependency injection so, first off, create a folder named Data then, create an interface named ITwitterAuthRepository and a class TwitterAuthRepository.

public interface ITwitterAuthRepository
{
    Task<RequestTokenResponse> GetRequestToken();

}
Enter fullscreen mode Exit fullscreen mode

TwitterAuthRepository class and add implementation.

public class TwitterAuthRepository : ITwitterAuthRepository
{
    private readonly IConfiguration _config;
    private readonly IHttpClientFactory _clientFactory;
    private readonly IOptions<TwitterSettings> _twitterConfig;
    private readonly DataContext _context;

    public TwitterAuthRepository(IConfiguration config, IHttpClientFactory clientFactory, IOptions<TwitterSettings> twitterConfig, DataContext context)
    {
        _context = context;
        _twitterConfig = twitterConfig;
        _clientFactory = clientFactory;
        _config = config;

    }
 }
Enter fullscreen mode Exit fullscreen mode

To start a sign-in flow, your Twitter app must obtain a request token by sending a signed message to POST oauth/request_token. The only unique parameter in this request is oauth_callback, which must be a URL-encoded version of the URL you wish your user to be redirected to when they complete step 2. The remaining parameters are added by the OAuth signing process.

Twitter settings model.

public class TwitterSettings
{
    public string AppId { get; set; }
    public string AppSecret { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Add this in your appsettings.json

"TwitterSettings": {
      "AppId": "",
      "AppSecret": ""
 }
Enter fullscreen mode Exit fullscreen mode

configure this in your startup class

services.Configure<TwitterSettings>(Configuration.GetSection("TwitterSettings"));
services.AddHttpClient("twitter");
services.AddScoped<ITwitterAuthRepository, TwitterAuthRepository>();
Enter fullscreen mode Exit fullscreen mode

Install the nuget package OAuth.DotNetCore.

public async Task<RequestTokenResponse> GetRequestToken()
{

        var requestTokenResponse = new RequestTokenResponse();


        var client = _clientFactory.CreateClient("twitter");
        var consumerKey = _twitterConfig.Value.AppId;
        var consumerSecret = _twitterConfig.Value.AppSecret;
        var callbackUrl = "http://localhost:4200/login";

        client.DefaultRequestHeaders.Accept.Clear();

        var oauthClient = new OAuthRequest
        {
            Method = "POST",
            Type = OAuthRequestType.RequestToken,
            SignatureMethod = OAuthSignatureMethod.HmacSha1,
            ConsumerKey = consumerKey,
            ConsumerSecret = consumerSecret,
            RequestUrl = "https://api.twitter.com/oauth/request_token",
            Version = "1.0a",
            Realm = "twitter.com",
            CallbackUrl = callbackUrl
        };

        string auth = oauthClient.GetAuthorizationHeader();

        client.DefaultRequestHeaders.Add("Authorization", auth);



        try
        {
            var content = new StringContent("", Encoding.UTF8, "application/json");

            using (var response = await client.PostAsync(oauthClient.RequestUrl, content))
            {
                response.EnsureSuccessStatusCode();

                var responseString = response.Content.ReadAsStringAsync()
                                           .Result.Split("&");


                requestTokenResponse = new RequestTokenResponse
                {
                    oauth_token = responseString[0],
                    oauth_token_secret = responseString[1],
                    oauth_callback_confirmed = responseString[2]
                };


            }
        }
        catch (Exception ex)
        {

            throw;
        }

        return requestTokenResponse;

    }
Enter fullscreen mode Exit fullscreen mode

Create a controller

[Route("api/[controller]")]
[ApiController]
public class TwitterClientController : ControllerBase
{
    private readonly ITwitterAuthRepository _twitterAuth;
    private readonly IMapper _mapper;
    private readonly IConfiguration _config;
    private readonly DataContext _context;
    public TwitterClientController(ITwitterAuthRepository twitterAuth, IMapper mapper, 
    IConfiguration config, DataContext context)
    {
        _context = context;
        _config = config;
        _mapper = mapper;
        _twitterAuth = twitterAuth;

    }

[HttpGet("GetRequestToken")]
public async Task<IActionResult> GetRequestToken()
{
    //STEP 1 call made to /oauth/request_token
    var response = await _twitterAuth.GetRequestToken();

    return Ok(response);

}
Enter fullscreen mode Exit fullscreen mode

}

Step 2: Redirecting the user

Create a service and a model on your SPA.

Service

export class TwitterAuthService {
   baseUrl = "http://localhost:5000/api/";

   constructor(private http: HttpClient) { }

getRequestToken(): Observable<RequestToken> {
   return this.http.get<RequestToken>(this.baseUrl +'twitterclient/GetRequestToken');
}
}
Enter fullscreen mode Exit fullscreen mode

Model

export interface RequestToken {
  oauth_token: string,
  oauth_token_secret: string,
  oauth_callback_confirmed: string
}
Enter fullscreen mode Exit fullscreen mode

Create a login component

Add this to your login.component.ts file

 export class LoginComponent implements OnInit {

   private requestToken: Partial<RequestToken> = {};
   disableButton = false;
   isLoading = false;

   constructor(private twitterService: TwitterAuthService,   private route: ActivatedRoute, private router: Router) { }

   ngOnInit() {

   }

launchTwitterLogin() {
 this.isLoading = true;
 this.disableButton = true;
 this.twitterService.getRequestToken()
  .subscribe(response => this.requestToken = response, 
    error => console.log(error), 
    () => {
    location.href = "https://api.twitter.com/oauth/authenticate?" + this.requestToken.oauth_token;
    }
  );
 }
}
Enter fullscreen mode Exit fullscreen mode

create a sign in button in your login.component.html

 <button class="btn btn-info btn-block (click)="launchTwitterLogin()" type="button" [disabled]="disableButton"> <i *ngIf="isLoading" class="fa fa-spinner fa-spin fa-lg fa-fw"></i> <i class="fa fa-twitter"></i> Sign in with <b>Twitter</b>
      </button>
Enter fullscreen mode Exit fullscreen mode

Step 3: Converting the request token to an access token

To render the request token into a usable access token, your application must make a request to the POST oauth/access_token endpoint, containing the oauth_verifier value obtained in step 2. The request token is also passed in the oauth_token portion of the header, but this will have been added by the signing process.

Model

 public class UserModelDto
 {
    public string Username { get; set; }
    public string UserId { get; set; }
    public string Token { get; set; }
    public string TokenSecret { get; set; }

 }
Enter fullscreen mode Exit fullscreen mode

Add this to TwiterAuthRepository.cs

 //Get Access Token
    public async Task<UserModelDto> GetAccessToken(string token, string oauthVerifier)
    {
        var client = _clientFactory.CreateClient("twitter");
        var consumerKey = _twitterConfig.Value.AppId;
        var consumerSecret = _twitterConfig.Value.AppSecret;

        var accessTokenResponse = new UserModelDto();

        client.DefaultRequestHeaders.Accept.Clear();

        var oauthClient = new OAuthRequest
        {
            Method = "POST",
            Type = OAuthRequestType.AccessToken,
            SignatureMethod = OAuthSignatureMethod.HmacSha1,
            ConsumerKey = consumerKey,
            ConsumerSecret = consumerSecret,
            RequestUrl = "https://api.twitter.com/oauth/access_token",
            Token = token,
            Version = "1.0a",
            Realm = "twitter.com"
        };

        string auth = oauthClient.GetAuthorizationHeader();

        client.DefaultRequestHeaders.Add("Authorization", auth);


        try
        {
            var content = new FormUrlEncodedContent(new[]{
                new KeyValuePair<string, string>("oauth_verifier", oauthVerifier)
            });

            using (var response = await client.PostAsync(oauthClient.RequestUrl, content))
            {
                response.EnsureSuccessStatusCode();

                //twiiter responds with a string concatenated by &
                var responseString = response.Content.ReadAsStringAsync()
                                           .Result.Split("&");

                //split by = to get actual values
                accessTokenResponse = new UserModelDto 
                {
                    Token = responseString[0].Split("=")[1],
                    TokenSecret = responseString[1].Split("=")[1],
                    UserId = responseString[2].Split("=")[1],
                    Username = responseString[3].Split("=")[1]
                };

            }
        }
        catch (Exception ex)
        {


        }

        return accessTokenResponse;
    }
Enter fullscreen mode Exit fullscreen mode

Add this to your controller

   [HttpGet("sign-in-with-twitter")]
public async Task<IActionResult> SignInWithTwitter(string oauth_token, string oauth_verifier)
{

    var response = await _twitterAuth.GetAccessToken(oauth_token, oauth_verifier);


    return Ok(response);

}
Enter fullscreen mode Exit fullscreen mode

Update the constructor on your login component

  constructor(private twitterService: TwitterAuthService, private route: ActivatedRoute, private router: Router) { 

this.route.queryParamMap.subscribe(params => {
  const oauth_token = this.route.snapshot.queryParamMap.get('oauth_token');
  const oauth_verifier = this.route.snapshot.queryParamMap.get("oauth_verifier");
  if (oauth_token && oauth_verifier) {
    this.disableButton = true;
    this.isLoading = true;
    this.twitterService.getAccessToken(oauth_token, oauth_verifier).subscribe(null, error => console.log(error)
    ,() => {
      this.router.navigate(['/home']);
    });



  }
});

}
Enter fullscreen mode Exit fullscreen mode

A successful response contains the oauth_token, oauth_token_secret parameters. The token and token secret should be stored and used for future authenticated requests to the Twitter API. To determine the identity of the user, use GET account/verify_credentials.

Thank you.

Latest comments (2)

Collapse
 
andypiper profile image
Andy Piper

Nice tutorial!

For cases where you don't need the full sign-in with Twitter flow, but do want to use the Twitter APIs, I've built a minimal sample here, using the same OAuth DotNet package.

Collapse
 
smiththe_4th profile image
Gbubemi

Thank you. I would check it out