I'm currently creating a web application on which the user can login via his Google account. This works client side but I would also like to secure REST API calls. To do so, I send the "Google id token" with each request via the "Authorization" header. Now, I would like to verify in C# that the token passed is valid. I found that there is a .NET library to do so but I didn't find anywhere any clear documentation on how to simply validate the token.
Does anyone have some pointer for this?
My answer is the same as the answer above with a little bit more details.
using Google.Apis.Auth;
using Google.Apis.Auth.OAuth2;
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(Token);
...
The payload object contains all the information that you need.
According to the "Verify the integrity of the ID token" documentation multiple things must be checked, for the id token to be valid, not just the signature.
One of those is whether "the ID token is equal to [...] your app's client IDs". Since we never give the client ID to GoogleJsonWebSignature.ValidateAsync(token) it seems we need to check it manually. I'm assuming it's really just checking the signature and we need to do all of the other checks manually.
My first shot at this:
bool valid = true;
try
{
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(Token);
if (!payload.Audience.Equals("YOUR_CLIENT_ID_1234567890.apps.googleusercontent.com"))
valid = false;
if (!payload.Issuer.Equals("accounts.google.com") && !payload.Issuer.Equals("https://accounts.google.com"))
valid = false;
if (payload.ExpirationTimeSeconds == null)
valid = false;
else
{
DateTime now = DateTime.Now.ToUniversalTime();
DateTime expiration = DateTimeOffset.FromUnixTimeSeconds((long)payload.ExpirationTimeSeconds).DateTime;
if (now > expiration)
{
valid = false;
}
}
}
catch (InvalidJwtException e)
{
valid = false;
}
For future reference the following verifications are checked internally by the Google.Apis.Auth library and no extra validations are required (both passing settings or checking the payload):
bad jwt (null, empty, too long, missing signature)
wrong algorithm
invalid signature
invalid issuer
signature time without tolerance
The following however require input by the developer in order to be validated. They can be passed with GoogleJsonWebSignature.ValidationSettings:
audience
hosted domain
signature time with tolerance
Source: Google.Apis.Auth.Tests/GoogleJsonWebSignatureTests.cs
According to the docs, the token must be validated by verifying the signature with Google's public key. Also check the aus, iss and exp claims, and the hd claim if applies.
Therefore only the aus (and hd) have to be tested explicitly by the developer.
try
{
//...
var validationSettings = new GoogleJsonWebSignature.ValidationSettings
{
Audience = new string[] { "[google-signin-client_id].apps.googleusercontent.com" }
};
var payload = await GoogleJsonWebSignature.ValidateAsync(idToken, validationSettings);
//...
}
catch (InvalidJwtException ex)
{
//...
}
Yet another simplified answer (for .net 6):
Add this nuget package to your project:
https://www.nuget.org/packages/Google.Apis.Auth
Add using statement:
using Google.Apis.Auth;
Create this method in your controller:
[AllowAnonymous]
[HttpPost("verify")]
public async Task<ActionResult> Verify(){
string token = Request.Headers["Authorization"].ToString().Remove(0,7); //remove Bearer
var payload = await VerifyGoogleTokenId(token);
if (payload==null)
{
return BadRequest("Invalid token");
}
return Ok(payload); }
Create the VerifyGoogleTokenId function:
public async Task<GoogleJsonWebSignature.Payload> VerifyGoogleTokenId(string token){
try
{
// uncomment these lines if you want to add settings:
// var validationSettings = new GoogleJsonWebSignature.ValidationSettings
// {
// Audience = new string[] { "yourServerClientIdFromGoogleConsole.apps.googleusercontent.com" }
// };
// Add your settings and then get the payload
// GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(token, validationSettings);
// Or Get the payload without settings.
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(token);
return payload;
}
catch (System.Exception)
{
Console.WriteLine("invalid google token");
}
return null;
}
Test the implementation by sending a post request to yourapi.com/verify. Dont forget the authorization header.
Say thanks with an up vote.
Related
The system administrator enabled 2FA so I'm having to go through and update some programs to utilizes this for accessing the Dynamics API. Otherwise, we received the following:
{
"error":"interaction_required",
"error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000007-0000-0000-c000-000000000000'.\r\nTrace ID: 24822bc6-9e93-476d-8580-fd04e3889300\r\nCorrelation ID: efd5dbc5-dead-4665-a5a6-570ae15a55fb\r\nTimestamp: 2020-02-24 20:35:15Z",
"error_codes":[
50076
],
"timestamp":"2020-02-24 20:35:15Z",
"trace_id":"24822bc6-9e93-476d-8580-fd04e3889300",
"correlation_id":"efd5dbc5-dead-4665-a5a6-570ae15a55fb",
"error_uri":"https://login.windows.net/error?code=50076",
"suberror":"basic_action"
}
This article makes it sound pretty straight forward and is the process we had to use for Outlook and other apps. Basically, generating an App Password.
However, I'm trying to use the App Password instead of the Default password we've used for a while and still am unable to get an access token for the program to use.
Here is what we've been using:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CrmQbInvoiceSync
{
class CrmAuthorization
{
// Serialize the JSON response for the access_token
public class AccessToken
{
public string access_token { get; set; }
}
public static async Task<string> GetCrmAccessToken()
{
var values = new Dictionary<string, string>
{
// Connection parameters
{"client_id", ConfigurationManager.AppSettings["clientId"]},
{"client_secret", ConfigurationManager.AppSettings["clientSecret"]},
{"resource", ConfigurationManager.AppSettings["crmOrg"]},
{"username", ConfigurationManager.AppSettings["username"]},
{"password", ConfigurationManager.AppSettings["userPassword"]},
{"grant_type", "password"}
};
// Console.WriteLine(values);
// Convert to x-www-form-urlencoded
var content = new FormUrlEncodedContent(values);
try
{
// Send the x-www-form-urlencoded info to the OAuth2 end point
HttpResponseMessage response = await Services.Client.PostAsync(ConfigurationManager.AppSettings["crmUri"], content);
// Get the body from the response
var responseContent = await response.Content.ReadAsStringAsync();
// Extract out the access token from the response
AccessToken responseBody = JsonConvert.DeserializeObject<AccessToken>(responseContent);
// Test if there is an access token present
if (responseBody.access_token != null)
{
// If there is an access token, take it and use it in
// generating the query
var accessToken = responseBody.access_token;
return accessToken;
}
else
{
var accessToken = "Could not get the access token.";
Services.WriteLogFile(accessToken);
Console.WriteLine(accessToken);
return null;
}
}
catch (Exception e)
{
var error = e;
Services.WriteLogFile(error.ToString());
Console.WriteLine(error);
throw;
}
}
}
}
The {"password", ConfigurationManager.AppSettings["userPassword"]} line is what should be affected so I updated the AppSettings with the new App Password. Get this error, but seems like it should be working given I'm using the App Password:
Formatted JSON Data
{
"error":"invalid_grant",
"error_description":"AADSTS50126: Error validating credentials due to invalid username or password.\r\nTrace ID: 17bf1365-32a0-439e-bd99-9eaf8e3bab00\r\nCorrelation ID: 4d24cac1-dae9-49b7-961f-c7c739f885f4\r\nTimestamp: 2020-02-24 20:33:43Z",
"error_codes":[
50126
],
"timestamp":"2020-02-24 20:33:43Z",
"trace_id":"17bf1365-32a0-439e-bd99-9eaf8e3bab00",
"correlation_id":"4d24cac1-dae9-49b7-961f-c7c739f885f4",
"error_uri":"https://login.windows.net/error?code=50126"
}
Really, not sure if I should be updating something else in the program to accommodate MFA, but articles I've read indicate I should just be generating the App Password and it should be good. Suggestions?
I suggest you use a refresh token to refresh the access token. With refresh token, you can bypass this limitation of MFA.
To get a refresh token, you need to follow Azure AD OAuth2 auth code flow to get a refresh token interactively. And then you can get a new token with the refresh token you got.
Notice that the refresh token should be kept in secret. If it was compromised, you can revoke all refresh tokens of a specific use with PowerShell Revoke-AzureADUserAllRefreshToken
I have the following function to call users from active directory use graph api.
This function is hit on each keyup of a text box. But i am getting following error
Code: TokenNotFound Message: User not found in token cache. Maybe the
server was restarted.
at the line
var user = await graphClient.Users.Request().GetAsync();
Entire function Below:
public async Task<string> GetUsersJSONAsync(string textValue)
{
// email = email ?? User.Identity.Name ?? User.FindFirst("preferred_username").Value;
var identifier = User.FindFirst(Startup.ObjectIdentifierType)?.Value;
var graphClient = _graphSdkHelper.GetAuthenticatedClient(identifier);
string usersJSON = await GraphService.GetAllUserJson(graphClient, HttpContext, textValue);
return usersJSON;
}
public static async Task<string> GetAllUserJson(GraphServiceClient graphClient, HttpContext httpContext, string textValue)
{
// if (email == null) return JsonConvert.SerializeObject(new { Message = "Email address cannot be null." }, Formatting.Indented);
try
{
// Load user profile.
var user = await graphClient.Users.Request().GetAsync();
return JsonConvert.SerializeObject(user.Where(u => !string.IsNullOrEmpty(u.Surname) && ( u.Surname.ToLower().StartsWith(textValue) || u.Surname.ToUpper().StartsWith(textValue.ToUpper()))), Formatting.Indented);
}
catch (ServiceException e)
{
switch (e.Error.Code)
{
case "Request_ResourceNotFound":
case "ResourceNotFound":
case "ErrorItemNotFound":
//case "itemNotFound":
// return JsonConvert.SerializeObject(new { Message = $"User '{email}' was not found." }, Formatting.Indented);
//case "ErrorInvalidUser":
// return JsonConvert.SerializeObject(new { Message = $"The requested user '{email}' is invalid." }, Formatting.Indented);
case "AuthenticationFailure":
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
case "TokenNotFound":
await httpContext.ChallengeAsync();
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
default:
return JsonConvert.SerializeObject(new { Message = "An unknown error has occured." }, Formatting.Indented);
}
}
}
// Gets an access token. First tries to get the access token from the token cache.
// Using password (secret) to authenticate. Production apps should use a certificate.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
_userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
var cca = new ConfidentialClientApplication(
_appId,
_redirectUri,
_credential,
_userTokenCache,
null);
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
try
{
var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
Can you help whats going wrong?
I know this is 4 months old - is this still an issue for you?
As the previous respondent pointed out, the error you're seeing is being thrown in the catch block in your code meant to handle an empty users collection.
In case you're stuck on this, or anyone else comes here - if you used this sample (or using ConfidentialClientApplication in any respect) and are throwing this exception, it's because your _userTokenCache has no users*. Of course, it's not because your AD has no users, otherwise you wouldn't be able to authenticate. Most likely, it is because a stale cookie in your browser is being passed as the access token to your authProvider. You can use Fiddler (or just check your localhost browser cookies) to find it (should be called AspNetCore.Cookies, but you may want to clear all of them).
If you're storing the tokencache in session (as the example is), remember that each time you start and stop the application, your working memory will be thrown out so the token provided by your browser will no longer match the new one your application will retrieve upon starting up again (unless, again, you've cleared the browser cookies).
*cca.Users is no longer used or supported by MSAL - you have to use cca.GetAccountsAsync(). If you have a deployed application running with the deprecated IUser implementation, you'll have to change this. Otherwise, in development your compiler will complain and not let you build, so you'll already know about this.
Looking at the code, it seems some chunks of logic are missing. For example, you got the method
public async Task<string> GetUserAccessTokenAsync(string userId)
but I can't see where this is being called. Besides that, I don't see the code for fetching a token from Azure AD either. Lastly, the error message you mention
Code: TokenNotFound Message: User not found in token cache. Maybe the server was restarted.
Seems like the error you're throwing
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
Since the code isn't complete, I will try and make an assumption on what might be going wrong.
Firstly, assuming you're using MSAL.Net, a step in the acquisition of a token is missing.
The general flow is (Using GetTokenByAuthorizationCodeAsync())
Client challenges the user
User gets redirected and logs in
Callback is called and the client receives a code from the login process
Pass the code to GetTokenByAuthorizationCodeAsync() to obtain an id_token and depending on the permissions an access token.
GetTokenByAuthorizationCodeAsync() will store the token in the cache
that has been provided to the ConfidentialClientApplication
Retrieve the token from the cache with AcquireTokenSilentAsync()
If we fail to retrieve a token from the cache with AcquireTokenSilentAsync(), we'll request a new one from via
AcquireTokenAsync()
Most of this flow seems to be in place in your code, but it could be you're missing the actual token acquisition. Since no token is retrieved, no user is added to the ConfidentialClientApplication, which means cca.Users.Any() returns false, resulting in an ServiceError
Assuming the whole flow is in place, and you're actually acquiring a token, my second assumption would be that the _memoryCache are different. The _memoryCache in which you saved your token differs from the one you use to acquire a token silently.
I would recommend reading the documentation on token acquisition to determine the type of retrieving is the right fit for your application.
EDIT
Actually, I assume your code is inspired by this example.
What's especially interesting is this part
public GraphServiceClient GetAuthenticatedClient(string userId)
{
_graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Passing tenant ID to the sample auth provider to use as a cache key
var accessToken = await _authProvider.GetUserAccessTokenAsync(userId);
...
}
return _graphClient;
}
What seems to be happening is that calling var user = await graphClient.Users.Request().GetAsync(); invokes the delegate that is provided to the GraphServiceClient. This in turn calls _authProvider.GetUserAccessTokenAsync(userId); which brings us to the public async Task<string> GetUserAccessTokenAsync(string userId) method. Our error most likely originates here, due to no Users being present in the ConfidentialClientApplication.Users collection
Hope this helps!
I recently posted a question which has been answered but led to this new problem. If interested, it can be seen at Previous post.
Intro
I am currently developing an application using AD-B2C as my identity provider. This is integrated into the solution using their guidelines at AD B2C graph, which uses openid-connect.
I need to use a form of email activation (outside of their register policy) and as such I need to be able to pass a value from the URL in the email, through the sign-up process at B2C and back to the redirection URL.
For this we use the state parameter.
Problem
In my OnRedirectToIdentityProvider I encrypt the state
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var temp = notification.ProtocolMessage.State;
// To be used later
var mycustomparameter = notification.OwinContext.Get<string>("mycustomparameter");
if (notification.ProtocolMessage.State != null)
{
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("mycustomparameter", "testing");
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
}
return Task.FromResult(0);
}
This works for all I can tell.
Now the user is passed to the sign in on the AD B2C and is after the login redirected back where the OnMessageReceived is triggered.
private Task OnMessageReceived(MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
string mycustomparameter;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("mycustomparameter", out mycustomparameter);
return Task.FromResult(0);
}
this is where it breaks. In the ...StateDataFormat.Unprotect(protectedState)
It throws an error System.Security.Cryptography.CryptographicException with the message "Error occurred during a cryptographic operation."
EDIT: Stacktrace:
System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(System.Func<byte[], byte[]> func, byte[] input) Unknown
System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(byte[] protectedData) Unknown
System.Web.dll!System.Web.Security.MachineKey.Unprotect(System.Web.Security.Cryptography.ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) Unknown
System.Web.dll!System.Web.Security.MachineKey.Unprotect(byte[] protectedData, string[] purposes) Unknown
Microsoft.Owin.Host.SystemWeb.dll!Microsoft.Owin.Host.SystemWeb.DataProtection.MachineKeyDataProtector.Unprotect(byte[] protectedData) Unknown
Microsoft.Owin.Security.dll!Microsoft.Owin.Security.DataProtection.AppBuilderExtensions.CallDataProtectionProvider.CallDataProtection.Unprotect(byte[] protectedData) Unknown
Microsoft.Owin.Security.dll!Microsoft.Owin.Security.DataHandler.SecureDataFormat<Microsoft.Owin.Security.AuthenticationProperties>.Unprotect(string protectedText) Unknown
IntellifyPortal.dll!IntellifyPortal.Startup.OnMessageReceived(Microsoft.Owin.Security.Notifications.MessageReceivedNotification notification) Line 171 C#
My attempts
I have tried specifying machine keys in the Web.config
I have tried messing with the "CallbackPath property in OpenIdConnectAuthenticationOptions, with no success.
I have tried a lot of diffent tweaks, but I can't seem to figure out why I can't "unprotect" the inbound state.
Any help is appreciated,
Best regards.
Update: Solution
I have decided to use an alternative method, which I found to work(hopefully it may of use to others):
Azure-sample which I used as guidance
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
// Accept Invitation Email
string testValue= notification.OwinContext.Get<string>("testValue");
string testValue2= notification.OwinContext.Get<string>("testValue2");
if (!string.IsNullOrEmpty(testValue) && !string.IsNullOrEmpty(testValue2))
{
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("testValue", testValue);
state.Dictionary.Add("testValue2", testValue2);
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
}
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
// Look for acceptInvitation
string testValue;
string testValue2;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("testValue", out testValue);
state.Dictionary.TryGetValue("testValue2", out testValue2);
// InvitationAccept / store values
if(!string.IsNullOrEmpty(testValue) && !string.IsNullOrEmpty(testValue2))
{
// How can I pass values to the redirect controller?
// Can I somehow transfer it from here to that destination
}
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
Final Question
I can now receive the values back as expected. These values has to be used in creating a relation between the new account and other accounts/groups in the application.
I therefore want to transfer these values back to the application (controller) for processing. I've tried storing the values in the context, in the response headers and in the claims to no avail. I guess this is because that this is the "middleware" and that the actual "redirect" happens directly from AD B2C thus not holding my params.
Can I somehow get the params back to the controller as well, without relying on the request URI (originating from the original user link) - Preferably directly in the claims, so that a user already logged in does not have to "re-signin" upon clicking the link.
How can I get my values (in the state, which are handled in the OnMessageRecieved) passed to the controller which is redirected to?
You're not supposed to decrypt the hint. Instead of this:
ProtocolMessage.State.Split('
Remove the hint so you only have encrypted data:
ProtocolMessage.State.Parameters["state"].Replace("OpenId.AuthenticationOptions=","")
Then you can you decrypt value of sate:
StateDataFormat.Unprotect("TC%$t43tj9358utj3")
It should deserialize to AuthenticationOptions.
I'm trying to use the Google+ API to access info for the authenticated user. I've copied some code from one of the samples, which works fine (below), however I'm having trouble making it work in a way I can reuse the token across app-launches.
I tried capturing the "RefreshToken" property and using provider.RefreshToken() (amongst other things) and always get a 400 Bad Request response.
Does anyone know how to make this work, or know where I can find some samples? The Google Code site doesn't seem to cover this :-(
class Program
{
private const string Scope = "https://www.googleapis.com/auth/plus.me";
static void Main(string[] args)
{
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "BLAH";
provider.ClientSecret = "BLAH";
var auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthentication);
var plus = new PlusService(auth);
plus.Key = "BLAH";
var me = plus.People.Get("me").Fetch();
Console.WriteLine(me.DisplayName);
}
private static IAuthorizationState GetAuthentication(NativeApplicationClient arg)
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(new[] { Scope });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
Console.Write(" Authorization Code: ");
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization(authCode, state);
}
}
Here is an example. Make sure you add a string setting called RefreshToken and reference System.Security or find another way to safely store the refresh token.
private static byte[] aditionalEntropy = { 1, 2, 3, 4, 5 };
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(new[] { PlusService.Scopes.PlusMe.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
string refreshToken = LoadRefreshToken();
if (!String.IsNullOrWhiteSpace(refreshToken))
{
state.RefreshToken = refreshToken;
if (arg.RefreshToken(state))
return state;
}
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
Console.Write(" Authorization Code: ");
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
var result = arg.ProcessUserAuthorization(authCode, state);
StoreRefreshToken(state);
return result;
}
private static string LoadRefreshToken()
{
return Encoding.Unicode.GetString(ProtectedData.Unprotect(Convert.FromBase64String(Properties.Settings.Default.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
}
private static void StoreRefreshToken(IAuthorizationState state)
{
Properties.Settings.Default.RefreshToken = Convert.ToBase64String(ProtectedData.Protect(Encoding.Unicode.GetBytes(state.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
Properties.Settings.Default.Save();
}
The general idea is as follows:
You redirect the user to Google's Authorization Endpoint.
You obtain a short-lived Authorization Code.
You immediately exchange the Authorization Code for a long-lived Access Token using Google's Token Endpoint. The Access Token comes with an expiry date and a Refresh Token.
You make requests to Google's API using the Access Token.
You can reuse the Access Token for as many requests as you like until it expires. Then you can use the Refresh Token to request a new Access Token (which comes with a new expiry date and a new Refresh Token).
See also:
The OAuth 2.0 Authorization Protocol
Google's OAuth 2.0 documentation
I also had problems with getting "offline" authentication to work (i.e. acquiring authentication with a refresh token), and got HTTP-response 400 Bad request with a code similar to the OP's code. However, I got it to work with the line client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret); in the Authenticate-method. This is essential to get a working code -- I think this line forces the clientSecret to be sent as a POST-parameter to the server (instead of as a HTTP Basic Auth-parameter).
This solution assumes that you've already got a client ID, a client secret and a refresh-token. Note that you don't need to enter an access-token in the code. (A short-lived access-code is acquired "under the hood" from the Google server when sending the long-lived refresh-token with the line client.RefreshAuthorization(state);. This access-token is stored as part of the auth-variable, from where it is used to authorize the API-calls "under the hood".)
A code example that works for me with Google API v3 for accessing my Google Calendar:
class SomeClass
{
private string clientID = "XXXXXXXXX.apps.googleusercontent.com";
private string clientSecret = "MY_CLIENT_SECRET";
private string refreshToken = "MY_REFRESH_TOKEN";
private string primaryCal = "MY_GMAIL_ADDRESS";
private void button2_Click_1(object sender, EventArgs e)
{
try
{
NativeApplicationClient client = new NativeApplicationClient(GoogleAuthenticationServer.Description, this.clientID, this.clientSecret);
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(client, Authenticate);
// Authenticated and ready for API calls...
// EITHER Calendar API calls (tested):
CalendarService cal = new CalendarService(auth);
EventsResource.ListRequest listrequest = cal.Events.List(this.primaryCal);
Google.Apis.Calendar.v3.Data.Events events = listrequest.Fetch();
// iterate the events and show them here.
// OR Plus API calls (not tested) - copied from OP's code:
var plus = new PlusService(auth);
plus.Key = "BLAH"; // don't know what this line does.
var me = plus.People.Get("me").Fetch();
Console.WriteLine(me.DisplayName);
// OR some other API calls...
}
catch (Exception ex)
{
Console.WriteLine("Error while communicating with Google servers. Try again(?). The error was:\r\n" + ex.Message + "\r\n\r\nInner exception:\r\n" + ex.InnerException.Message);
}
}
private IAuthorizationState Authenticate(NativeApplicationClient client)
{
IAuthorizationState state = new AuthorizationState(new string[] { }) { RefreshToken = this.refreshToken };
// IMPORTANT - does not work without:
client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);
client.RefreshAuthorization(state);
return state;
}
}
The OAuth 2.0 spec is not yet finished, and there is a smattering of spec implementations out there across the various clients and services that cause these errors to appear. Mostly likely you're doing everything right, but the DotNetOpenAuth version you're using implements a different draft of OAuth 2.0 than Google is currently implementing. Neither part is "right", since the spec isn't yet finalized, but it makes compatibility something of a nightmare.
You can check that the DotNetOpenAuth version you're using is the latest (in case that helps, which it might), but ultimately you may need to either sit tight until the specs are finalized and everyone implements them correctly, or read the Google docs yourself (which presumably describe their version of OAuth 2.0) and implement one that specifically targets their draft version.
I would recommend looking at the "SampleHelper" project in the Samples solution of the Google .NET Client API:
Samples/SampleHelper/AuthorizationMgr.cs
This file shows both how to use Windows Protected Data to store a Refresh token, and it also shows how to use a Local Loopback Server and different techniques to capture the Access code instead of having the user enter it manually.
One of the samples in the library which use this method of authorization can be found below:
Samples/Tasks.CreateTasks/Program.cs
I'm trying to update a user's Twitter status from my C# application.
I searched the web and found several possibilities, but I'm a bit confused by the recent (?) change in Twitter's authentication process. I also found what seems to be a relevant StackOverflow post, but it simply does not answer my question because it's ultra-specific regading a code snippet that does not work.
I'm attempting to reach the REST API and not the Search API, which means I should live up to the stricter OAuth authentication.
I looked at two solutions. The Twitterizer Framework worked fine, but it's an external DLL and I would rather use source code. Just as an example, the code using it is very clear and looks like so:
Twitter twitter = new Twitter("username", "password");
twitter.Status.Update("Hello World!");
I also examined Yedda's Twitter library, but this one failed on what I believe to be the authentication process, when trying basically the same code as above (Yedda expects the username and password in the status update itself but everything else is supposed to be the same).
Since I could not find a clear cut answer on the web, I'm bringing it to StackOverflow.
What's the simplest way to get a Twitter status update working in a C# application, without external DLL dependency?
Thanks
If you like the Twitterizer Framework but just don't like not having the source, why not download the source? (Or browse it if you just want to see what it's doing...)
I'm not a fan of re-inventing the wheel, especially when it comes to products that already exist that provide 100% of the sought functionality. I actually have the source code for Twitterizer running side by side my ASP.NET MVC application just so that I could make any necessary changes...
If you really don't want the DLL reference to exist, here is an example on how to code the updates in C#. Check this out from dreamincode.
/*
* A function to post an update to Twitter programmatically
* Author: Danny Battison
* Contact: gabehabe#hotmail.com
*/
/// <summary>
/// Post an update to a Twitter acount
/// </summary>
/// <param name="username">The username of the account</param>
/// <param name="password">The password of the account</param>
/// <param name="tweet">The status to post</param>
public static void PostTweet(string username, string password, string tweet)
{
try {
// encode the username/password
string user = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
// determine what we want to upload as a status
byte[] bytes = System.Text.Encoding.ASCII.GetBytes("status=" + tweet);
// connect with the update page
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://twitter.com/statuses/update.xml");
// set the method to POST
request.Method="POST";
request.ServicePoint.Expect100Continue = false; // thanks to argodev for this recent change!
// set the authorisation levels
request.Headers.Add("Authorization", "Basic " + user);
request.ContentType="application/x-www-form-urlencoded";
// set the length of the content
request.ContentLength = bytes.Length;
// set up the stream
Stream reqStream = request.GetRequestStream();
// write to the stream
reqStream.Write(bytes, 0, bytes.Length);
// close the stream
reqStream.Close();
} catch (Exception ex) {/* DO NOTHING */}
}
Another Twitter library I have used sucessfully is TweetSharp, which provides a fluent API.
The source code is available at Google code. Why don't you want to use a dll? That is by far the easiest way to include a library in a project.
The simplest way to post stuff to twitter is to use basic authentication , which isn't very strong.
static void PostTweet(string username, string password, string tweet)
{
// Create a webclient with the twitter account credentials, which will be used to set the HTTP header for basic authentication
WebClient client = new WebClient { Credentials = new NetworkCredential { UserName = username, Password = password } };
// Don't wait to receive a 100 Continue HTTP response from the server before sending out the message body
ServicePointManager.Expect100Continue = false;
// Construct the message body
byte[] messageBody = Encoding.ASCII.GetBytes("status=" + tweet);
// Send the HTTP headers and message body (a.k.a. Post the data)
client.UploadData("http://twitter.com/statuses/update.xml", messageBody);
}
Try LINQ To Twitter. Find LINQ To Twitter update status with media complete code example that works with Twitter REST API V1.1. Solution is also available for download.
LINQ To Twitter Code Sample
var twitterCtx = new TwitterContext(auth);
string status = "Testing TweetWithMedia #Linq2Twitter " +
DateTime.Now.ToString(CultureInfo.InvariantCulture);
const bool PossiblySensitive = false;
const decimal Latitude = StatusExtensions.NoCoordinate;
const decimal Longitude = StatusExtensions.NoCoordinate;
const bool DisplayCoordinates = false;
string ReplaceThisWithYourImageLocation = Server.MapPath("~/test.jpg");
var mediaItems =
new List<media>
{
new Media
{
Data = Utilities.GetFileBytes(ReplaceThisWithYourImageLocation),
FileName = "test.jpg",
ContentType = MediaContentType.Jpeg
}
};
Status tweet = twitterCtx.TweetWithMedia(
status, PossiblySensitive, Latitude, Longitude,
null, DisplayCoordinates, mediaItems, null);
Try TweetSharp . Find TweetSharp update status with media complete code example works with Twitter REST API V1.1. Solution is also available for download.
TweetSharp Code Sample
//if you want status update only uncomment the below line of code instead
//var result = tService.SendTweet(new SendTweetOptions { Status = Guid.NewGuid().ToString() });
Bitmap img = new Bitmap(Server.MapPath("~/test.jpg"));
if (img != null)
{
MemoryStream ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
Dictionary<string, Stream> images = new Dictionary<string, Stream>{{"mypicture", ms}};
//Twitter compares status contents and rejects dublicated status messages.
//Therefore in order to create a unique message dynamically, a generic guid has been used
var result = tService.SendTweetWithMedia(new SendTweetWithMediaOptions { Status = Guid.NewGuid().ToString(), Images = images });
if (result != null && result.Id > 0)
{
Response.Redirect("https://twitter.com");
}
else
{
Response.Write("fails to update status");
}
}
Here's another solution with minimal code using the excellent AsyncOAuth Nuget package and Microsoft's HttpClient. This solution also assumes you're posting on your own behalf so you have your access token key/secret already, however even if you don't the flow is pretty easy (see AsyncOauth docs).
using System.Threading.Tasks;
using AsyncOAuth;
using System.Net.Http;
using System.Security.Cryptography;
public class TwitterClient
{
private readonly HttpClient _httpClient;
public TwitterClient()
{
// See AsyncOAuth docs (differs for WinRT)
OAuthUtility.ComputeHash = (key, buffer) =>
{
using (var hmac = new HMACSHA1(key))
{
return hmac.ComputeHash(buffer);
}
};
// Best to store secrets outside app (Azure Portal/etc.)
_httpClient = OAuthUtility.CreateOAuthClient(
AppSettings.TwitterAppId, AppSettings.TwitterAppSecret,
new AccessToken(AppSettings.TwitterAccessTokenKey, AppSettings.TwitterAccessTokenSecret));
}
public async Task UpdateStatus(string status)
{
try
{
var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"status", status}
});
var response = await _httpClient.PostAsync("https://api.twitter.com/1.1/statuses/update.json", content);
if (response.IsSuccessStatusCode)
{
// OK
}
else
{
// Not OK
}
}
catch (Exception ex)
{
// Log ex
}
}
}
This works on all platforms due to HttpClient's nature. I use this method myself on Windows Phone 7/8 for a completely different service.