Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get an OAuth 2.0 authentication token in C#

I have these settings:

  • Auth URL (which happens to be a "https://login.microsoftonline.com/...") if that helps.
  • Access Token URL "https://service.endpoint.com/api/oauth2/token"
  • ClientId "abc"
  • Clientsecret "123"

I then need to make a get call using a bearer token in the header.

I can get this to work in Postman, but have hit a wall trying to work out how to implement it in C#. I've been using RestSharp (but open to others). It all seems so opaque, when I thought it'd be pretty straight forward: it's a console app, so I don't need bells and whistles.

Ultimately, I want my app to (programatically) get a token, then use that for my subsequent calls. I'd appreciate anyone pointing me to documentation or examples, that explains what I'm after clearly. Everything I've come across is partial or for services operating on a different flow.

Thanks.

like image 783
Matt C Avatar asked Jul 21 '16 03:07

Matt C


People also ask

What is OAuth 2.0 authentication C#?

Email supports OAuth 2.0 authentication to connect with POP, IMAP, SMTP or EWS protocols and access email data. The OAuth 2.0 authorization framework is a protocol that enables a third-party application to obtain limited access to the user's protected resources without using the resource owner's credentials.


2 Answers

In Postman, click Generate Code and then in Generate Code Snippets dialog you can select a different coding language, including C# (RestSharp).

Also, you should only need the access token URL. The form parameters are then:

grant_type=client_credentials client_id=abc     client_secret=123 

Code Snippet:

/* using RestSharp; // https://www.nuget.org/packages/RestSharp/ */  var client = new RestClient("https://service.endpoint.com/api/oauth2/token"); var request = new RestRequest(Method.POST); request.AddHeader("cache-control", "no-cache"); request.AddHeader("content-type", "application/x-www-form-urlencoded"); request.AddParameter("application/x-www-form-urlencoded", "grant_type=client_credentials&client_id=abc&client_secret=123", ParameterType.RequestBody); IRestResponse response = client.Execute(request); 

From the response body you can then obtain your access token. For instance for a Bearer token type you can then add the following header to subsequent authenticated requests:

request.AddHeader("authorization", "Bearer <access_token>"); 
like image 51
Stav Avatar answered Oct 18 '22 11:10

Stav


The Rest Client answer is perfect! (I upvoted it)

But, just in case you want to go "raw"

..........

I got this to work with HttpClient.

"abstractly" what you are doing is

  1. creating a POST request.
  2. with a body of payload "type" of 'x-www-form-urlencoded'. ( see FormUrlEncodedContent https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent?view=net-5.0 and note the constructor : https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent.-ctor?view=net-5.0)
  3. and in the payload of 'type' : x-www-form-urlencoded, you are putting in certain values like the grant_type, client_id, client_secret etc.

Side note, try to get it working in PostMan, and then it is easier to "code it up" using the code below.

But here we go, code using HttpClient.

.......

/* .nuget\packages\newtonsoft.json\12.0.1 .nuget\packages\system.net.http\4.3.4 */          using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web;               private static async Task<Token> GetElibilityToken(HttpClient client)     {         string baseAddress = @"https://blah.blah.blah.com/oauth2/token";          string grant_type = "client_credentials";         string client_id = "myId";         string client_secret = "shhhhhhhhhhhhhhItsSecret";          var form = new Dictionary<string, string>                 {                     {"grant_type", grant_type},                     {"client_id", client_id},                     {"client_secret", client_secret},                 };          HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));         var jsonContent = await tokenResponse.Content.ReadAsStringAsync();         Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);         return tok;     }           internal class Token {     [JsonProperty("access_token")]     public string AccessToken { get; set; }      [JsonProperty("token_type")]     public string TokenType { get; set; }      [JsonProperty("expires_in")]     public int ExpiresIn { get; set; }      [JsonProperty("refresh_token")]     public string RefreshToken { get; set; } }        

Here is another working example (based off the answer above)......with a few more tweaks. Sometimes the token-service is finicky:

    private static async Task<Token> GetATokenToTestMyRestApiUsingHttpClient(HttpClient client)     {         /* this code has lots of commented out stuff with different permutations of tweaking the request  */          /* this is a version of asking for token using HttpClient.  aka, an alternate to using default libraries instead of RestClient */          OAuthValues oav = GetOAuthValues(); /* object has has simple string properties for TokenUrl, GrantType, ClientId and ClientSecret */          var form = new Dictionary<string, string>                 {                     { "grant_type", oav.GrantType },                     { "client_id", oav.ClientId },                     { "client_secret", oav.ClientSecret }                 };          /* now tweak the http client */         client.DefaultRequestHeaders.Clear();         client.DefaultRequestHeaders.Add("cache-control", "no-cache");          /* try 1 */         ////client.DefaultRequestHeaders.Add("content-type", "application/x-www-form-urlencoded");          /* try 2 */         ////client.DefaultRequestHeaders            .Accept            .Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));//ACCEPT header          /* try 3 */         ////does not compile */client.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");          ////application/x-www-form-urlencoded          HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, oav.TokenUrl);         /////req.RequestUri = new Uri(baseAddress);                  req.Content = new FormUrlEncodedContent(form);          ////string jsonPayload = "{\"grant_type\":\"" + oav.GrantType + "\",\"client_id\":\"" + oav.ClientId + "\",\"client_secret\":\"" + oav.ClientSecret + "\"}";         ////req.Content = new StringContent(jsonPayload,                                                Encoding.UTF8,                                                "application/json");//CONTENT-TYPE header          req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");          /* now make the request */         ////HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));         HttpResponseMessage tokenResponse = await client.SendAsync(req);         Console.WriteLine(string.Format("HttpResponseMessage.ReasonPhrase='{0}'", tokenResponse.ReasonPhrase));          if (!tokenResponse.IsSuccessStatusCode)         {             throw new HttpRequestException("Call to get Token with HttpClient failed.");         }          var jsonContent = await tokenResponse.Content.ReadAsStringAsync();         Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);          return tok;     } 

APPEND

Bonus Material!

If you ever get a

"The remote certificate is invalid according to the validation procedure."

exception......you can wire in a handler to see what is going on (and massage if necessary)

using System; using System.Collections.Generic; using System.Text; using Newtonsoft.Json; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; using System.Net;  namespace MyNamespace {     public class MyTokenRetrieverWithExtraStuff     {         public static async Task<Token> GetElibilityToken()         {             using (HttpClientHandler httpClientHandler = new HttpClientHandler())             {                 httpClientHandler.ServerCertificateCustomValidationCallback = CertificateValidationCallBack;                 using (HttpClient client = new HttpClient(httpClientHandler))                 {                     return await GetElibilityToken(client);                 }             }         }          private static async Task<Token> GetElibilityToken(HttpClient client)         {             // throws certificate error if your cert is wired to localhost //              //string baseAddress = @"https://127.0.0.1/someapp/oauth2/token";              //string baseAddress = @"https://localhost/someapp/oauth2/token";          string baseAddress = @"https://blah.blah.blah.com/oauth2/token";          string grant_type = "client_credentials";         string client_id = "myId";         string client_secret = "shhhhhhhhhhhhhhItsSecret";          var form = new Dictionary<string, string>                 {                     {"grant_type", grant_type},                     {"client_id", client_id},                     {"client_secret", client_secret},                 };              HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));             var jsonContent = await tokenResponse.Content.ReadAsStringAsync();             Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);             return tok;         }          private static bool CertificateValidationCallBack(         object sender,         System.Security.Cryptography.X509Certificates.X509Certificate certificate,         System.Security.Cryptography.X509Certificates.X509Chain chain,         System.Net.Security.SslPolicyErrors sslPolicyErrors)         {             // If the certificate is a valid, signed certificate, return true.             if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)             {                 return true;             }              // If there are errors in the certificate chain, look at each error to determine the cause.             if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)             {                 if (chain != null && chain.ChainStatus != null)                 {                     foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)                     {                         if ((certificate.Subject == certificate.Issuer) &&                            (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))                         {                             // Self-signed certificates with an untrusted root are valid.                              continue;                         }                         else                         {                             if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)                             {                                 // If there are any other errors in the certificate chain, the certificate is invalid,                                 // so the method returns false.                                 return false;                             }                         }                     }                 }                  // When processing reaches this line, the only errors in the certificate chain are                  // untrusted root errors for self-signed certificates. These certificates are valid                 // for default Exchange server installations, so return true.                 return true;             }               /* overcome localhost and 127.0.0.1 issue */             if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch) != 0)             {                 if (certificate.Subject.Contains("localhost"))                 {                     HttpRequestMessage castSender = sender as HttpRequestMessage;                     if (null != castSender)                     {                         if (castSender.RequestUri.Host.Contains("127.0.0.1"))                         {                             return true;                         }                     }                 }             }              return false;          }           public class Token         {             [JsonProperty("access_token")]             public string AccessToken { get; set; }              [JsonProperty("token_type")]             public string TokenType { get; set; }              [JsonProperty("expires_in")]             public int ExpiresIn { get; set; }              [JsonProperty("refresh_token")]             public string RefreshToken { get; set; }         }      } } 

........................

I recently found (Jan/2020) an article about all this. I'll add a link here....sometimes having 2 different people show/explain it helps someone trying to learn it.

http://luisquintanilla.me/2017/12/25/client-credentials-authentication-csharp/

like image 45
granadaCoder Avatar answered Oct 18 '22 11:10

granadaCoder