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; }
}
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();
}
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;
}
}
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; }
}
Add this in your appsettings.json
"TwitterSettings": {
"AppId": "",
"AppSecret": ""
}
configure this in your startup class
services.Configure<TwitterSettings>(Configuration.GetSection("TwitterSettings"));
services.AddHttpClient("twitter");
services.AddScoped<ITwitterAuthRepository, TwitterAuthRepository>();
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;
}
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);
}
}
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');
}
}
Model
export interface RequestToken {
oauth_token: string,
oauth_token_secret: string,
oauth_callback_confirmed: string
}
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;
}
);
}
}
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>
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; }
}
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;
}
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);
}
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']);
});
}
});
}
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.
Top comments (2)
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.
Thank you. I would check it out