如何在 C # 中获得 OAuth 2.0身份验证令牌

我有这些设置:

然后,我需要使用标头中的持有者标记进行 get 调用。

我可以让它在 Postman 中工作,但是在尝试如何在 C # 中实现它时遇到了瓶颈。我一直在使用 RestSharp (但是对其他人开放)。这一切看起来如此不透明,当我认为它会相当直接: 这是一个控制台应用程序,所以我不需要铃铛和哨子。

最终,我希望我的应用程序(以编程方式)获得一个令牌,然后在后续调用中使用它。如果有人能给我提供文档或者示例,清楚地解释我的目标,我将不胜感激。我遇到的所有东西都是部分的,或者是运行在不同流上的服务。

谢谢。

240026 次浏览

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>");

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://learn.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent?view=net-5.0 and note the constructor : https://learn.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/

This example get token thouth HttpWebRequest

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(pathapi);
request.Method = "POST";
string postData = "grant_type=password";
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] byte1 = encoding.GetBytes(postData);


request.ContentType = "application/x-www-form-urlencoded";


request.ContentLength = byte1.Length;
Stream newStream = request.GetRequestStream();
newStream.Write(byte1, 0, byte1.Length);


HttpWebResponse response = request.GetResponse() as HttpWebResponse;
using (Stream responseStream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
getreaderjson = reader.ReadToEnd();
}

Clearly:

Server side generating a token example

private string GenerateToken(string userName)
{
var someClaims = new Claim[]{
new Claim(JwtRegisteredClaimNames.UniqueName, userName),
new Claim(JwtRegisteredClaimNames.Email, GetEmail(userName)),
new Claim(JwtRegisteredClaimNames.NameId,Guid.NewGuid().ToString())
};


SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Tokenizer.Key));
var token = new JwtSecurityToken(
issuer: _settings.Tokenizer.Issuer,
audience: _settings.Tokenizer.Audience,
claims: someClaims,
expires: DateTime.Now.AddHours(_settings.Tokenizer.ExpiryHours),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);


return new JwtSecurityTokenHandler().WriteToken(token);
}

(note: Tokenizer is my helper class that contains Issuer Audience etc..)

Definitely:

Client side getting a token for authentication

    public async Task<string> GetToken()
{
string token = "";
var siteSettings = DependencyResolver.Current.GetService<SiteSettings>();


var client = new HttpClient();
client.BaseAddress = new Uri(siteSettings.PopularSearchRequest.StaticApiUrl);
client.DefaultRequestHeaders.Accept.Clear();
//client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));


StatisticUserModel user = new StatisticUserModel()
{
Password = siteSettings.PopularSearchRequest.Password,
Username = siteSettings.PopularSearchRequest.Username
};


string jsonUser = JsonConvert.SerializeObject(user, Formatting.Indented);
var stringContent = new StringContent(jsonUser, Encoding.UTF8, "application/json");
var response = await client.PostAsync(siteSettings.PopularSearchRequest.StaticApiUrl + "/api/token/new", stringContent);
token = await response.Content.ReadAsStringAsync();


return token;
}

You can use this token for the authorization (that is in the subsequent requests)

Here is a complete example. Right click on the solution to manage nuget packages and get Newtonsoft and RestSharp:

using Newtonsoft.Json.Linq;
using RestSharp;
using System;




namespace TestAPI
{
class Program
{
static void Main(string[] args)
{
string id = "xxx";
string secret = "xxx";


var client = new RestClient("https://xxx.xxx.com/services/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&scope=all&client_id=" + id + "&client_secret=" + secret, ParameterType.RequestBody);
RestResponse response = client.Execute(request);


dynamic resp = JObject.Parse(response.Content);
string token = resp.access_token;


client = new RestClient("https://xxx.xxx.com/services/api/x/users/v1/employees");
request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer " + token);
request.AddHeader("cache-control", "no-cache");
response = client.Execute(request);
}
}
}

I used ADAL.NET/ Microsoft Identity Platform to achieve this. The advantage of using it was that we get a nice wrapper around the code to acquire AccessToken and we get additional features like Token Cache out-of-the-box. From the documentation:

Why use ADAL.NET ?

ADAL.NET V3 (Active Directory Authentication Library for .NET) enables developers of .NET applications to acquire tokens in order to call secured Web APIs. These Web APIs can be the Microsoft Graph, or 3rd party Web APIs.

Here is the code snippet:

    // Import Nuget package: Microsoft.Identity.Client
public class AuthenticationService
{
private readonly List<string> _scopes;
private readonly IConfidentialClientApplication _app;


public AuthenticationService(AuthenticationConfiguration authentication)
{


_app = ConfidentialClientApplicationBuilder
.Create(authentication.ClientId)
.WithClientSecret(authentication.ClientSecret)
.WithAuthority(authentication.Authority)
.Build();


_scopes = new List<string> {$"{authentication.Audience}/.default"};
}


public async Task<string> GetAccessToken()
{
var authenticationResult = await _app.AcquireTokenForClient(_scopes)
.ExecuteAsync();
return authenticationResult.AccessToken;
}
}

https://github.com/IdentityModel/IdentityModel adds extensions to HttpClient to acquire tokens using different flows and the documentation is great too. It's very handy because you don't have to think how to implement it yourself. I'm not aware if any official MS implementation exists.

You may use the following code to get the bearer token.

private string GetBearerToken()
{
var client = new RestClient("https://service.endpoint.com");
client.Authenticator = new HttpBasicAuthenticator("abc", "123");
var request = new RestRequest("api/oauth2/token", Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{ \"grant_type\":\"client_credentials\" }",
ParameterType.RequestBody);
var responseJson = _client.Execute(request).Content;
var token = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseJson)["access_token"].ToString();
if(token.Length == 0)
{
throw new AuthenticationException("API authentication failed.");
}
return token;
}

I tried this way to get OAuth 2.0 authentication token using c#

namespace ConsoleApp2
{


class Program
{
static void Main(string[] args)
{
Console.WriteLine(GetToken());
Console.Read();
}


/// <summary>
/// Get access token from api
/// </summary>
/// <returns></returns>
private static string GetToken()
{
string wClientId = "#######";
string wClientSecretKey = "*********************";
string wAccessToken;


//--------------------------- Approch-1 to get token using HttpClient -------------------------------------------------------------------------------------
HttpResponseMessage responseMessage;
using (HttpClient client = new HttpClient())
{
HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://localhost:1001/oauth/token");
HttpContent httpContent = new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
});
tokenRequest.Content = httpContent;
tokenRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(wClientId + ":" + wClientSecretKey)));
responseMessage =  client.SendAsync(tokenRequest).Result;
}
string ResponseJSON=   responseMessage.Content.ReadAsStringAsync().Result;




//--------------------------- Approch-2 to get token using HttpWebRequest and deserialize json object into ResponseModel class -------------------------------------------------------------------------------------




byte[] byte1 = Encoding.ASCII.GetBytes("grant_type=client_credentials");


HttpWebRequest oRequest = WebRequest.Create("https://localhost:1001/oauth/token") as HttpWebRequest;
oRequest.Accept = "application/json";
oRequest.Method = "POST";
oRequest.ContentType = "application/x-www-form-urlencoded";
oRequest.ContentLength = byte1.Length;
oRequest.KeepAlive = false;
oRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(wClientId + ":" + wClientSecretKey)));
Stream newStream = oRequest.GetRequestStream();
newStream.Write(byte1, 0, byte1.Length);


WebResponse oResponse = oRequest.GetResponse();


using (var reader = new StreamReader(oResponse.GetResponseStream(), Encoding.UTF8))
{
var oJsonReponse = reader.ReadToEnd();
ResponseModel oModel = JsonConvert.DeserializeObject<ResponseModel>(oJsonReponse);
wAccessToken = oModel.access_token;
}


return wAccessToken;
}
}
  

//----------------------------------------------------------------------------------------------------------------------------------------------------
//---------------------------------- Response Class---------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------------------


/// <summary>
/// De-serialize Web response Object into model class to read
/// </summary>
public class ResponseModel
{
public string scope { get; set; }
public string token_type { get; set; }
public string expires_in { get; set; }
public string refresh_token { get; set; }
public string access_token { get; set; }
}
}

My client is using grant_type=Authorization_code workflow.

I have below 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: "xyz" Clientsecret: "123dfsdf"

I then need to make a get call using a bearer token in the header. I tried all the above code sample, But whatever I will land on Microsoft - Sign in to you account" page as

"\r\n\r\n<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->\r\n<!DOCTYPE html>\r\n<html dir=\"ltr\" class=\"\" lang=\"en\">\r\n<head>\r\n    <title>Sign in to your account</title>\r\n "

I am able to execute on Postman and I observed there are 2 calls in console.

  1. GET call for Authorization Code
  2. POST call with above Authorization Code appended in the call to the the Access token.

I tried to execute the above GET call in a separately in POSTMAN, when I do that I will be prompted with microsoftonline login page, when I enter my credentials I will get salesforce error.

if anyone can provide sample code or examples of Authorization_code grand_type workflow That will be very great help...