I tried to implement Yahoo OAuth 2.0 with following guide: https://developer.yahoo.com/oauth2/guide/
I was able to authorize and I received Authorization Code but I sucked at step 4. When my code tries to exchange Authorization Code for Access Token using /get_token 401 Unauthorized Error is thrown.
{
"error": "invalid_grant"
}
According to https://developer.yahoo.com/oauth2/guide/errors/index.html invalid_grant means that an invalid or expired token was provided. I am little bit confused why the 401 is thrown.
Have somebody experienced similar issue?
public class YahooOAuthClient : OAuth2Client
{
private const string AuthorizeUrl = "https://api.login.yahoo.com/oauth2/request_auth";
private const string TokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token";
private readonly string clientId;
private readonly string clientSecret;
public YahooOAuthClient(string clientId, string clientSecret)
: base("Yahoo")
{
this.clientId = clientId;
this.clientSecret = clientSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var uriBuilder = new UriBuilder(AuthorizeUrl);
uriBuilder.AppendQueryArgument("client_id", this.clientId);
uriBuilder.AppendQueryArgument("redirect_uri", returnUrl.ToString());
uriBuilder.AppendQueryArgument("response_type", "code");
uriBuilder.AppendQueryArgument("language", "en-us");
return uriBuilder.Uri;
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
return new Dictionary<string, string>
{
{"id", accessToken}
};
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var postData = HttpUtility.ParseQueryString(string.Empty);
postData.Add(new NameValueCollection
{
{ "grant_type", "authorization_code" },
{ "code", authorizationCode },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
String encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(clientId + ":" + clientSecret));
webRequest.Headers.Add("Authorization", "Basic " + encoded);
using (var s = webRequest.GetRequestStream())
using (var sw = new StreamWriter(s))
sw.Write(postData.ToString());
using (var webResponse = webRequest.GetResponse())
{
var responseStream = webResponse.GetResponseStream();
if (responseStream == null)
return null;
using (var reader = new StreamReader(responseStream))
{
var response = reader.ReadToEnd();
var json = JObject.Parse(response);
var accessToken = json.Value<string>("access_token");
return accessToken;
}
}
}
}
This answer may be late but I ran into the same problem as you though I was implementing with PHP. This is what I realized was my error:
Intially my return_url is of the form
http://example.com/oauth_handle?route=something/path/like¶m1=value¶m2=value
I was implemting oauth for facebook, twitter(blah), google linkedin and yahoo. Google oauth api didn't like the route=something/path/like so I url-encoded it to
http://example.com/oauth_handle?route=something%2Fpath%2Flike¶m1=value¶m2=value
So that is the return_uri that was sent to the /request_auth endpoint as-is.
Then for the /get_token then parameter return_uri was url-encoded again which means I was now doing a double url-encoding for the route=something/path/like part which makes the return_uri different in the two requests.
Correcting this by doing url_encode just once solves my problem. Hope this helps!
Related
Actually I'm trying to get Access Token using Using the TenantId after Admin consent successfully completed admin by using the following endpoint
https://login.microsoftonline.com/common/adminconsent
then I am requesting the AccessToken Endpoint Using Tenant ID
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
by using HttpWebRequest by passing all the required fields
tenant : 6292ef34-37a8-4687-b8b3-7dd8d54a8a42
Code for API call using HttpWebRequest
public static string AdminConsentAccessToken(string tenant, string state = "", string admin_concent = "")
{
string postData="{\"client_id\":\"65577dd2-cc76-46af-a1ac-71582eac6af2\",\"scope\":\"https://graph.microsoft.com/.default",\"client_secret\":\"my_client_secret\","\grant_type\":"\client_credentials\"}"
string result = string.Empty;
try
{
var httpRequest = (HttpWebRequest)WebRequest.Create($"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token");
httpRequest.Method = "POST"; ;
httpRequest.Accept = "application/json";
httpRequest.ContentType = "application/x-www-form-urlencoded";
using (var streamWriter = new StreamWriter(httpRequest.GetRequestStream()))
{
streamWriter.Write(postData);
}
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
result = streamReader.ReadToEnd();
}
}
catch (Exception ex)
{
//exception
}
return result;
}
With this code I am getting following error : "The remote server returned an error: (400) Bad Request." and status : Protocol Error, can you please check code suggest what went wrong.
and I have tried this from post man also the i am getting 400 - Bad Request (The request cannot be fulfilled due to bad syntax.)
POST : https://login.microsoftonline.com/6292ef34-37a8-4687-b8b3-7dd8d54a8a42/oauth2/v2.0/token
Header : Content-Type : application/x-www-form-urlencoded
Body :
{
"grant_type": "client_credentials",
"client_id":"65577dd2-cc76-46af-a1ac-71582eac6af2",
"scope":"https://graph.microsoft.com/.default",
"client_secret": "my_client_secret"
}
Response :
{
"error": "invalid_request",
"error_description": "AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: 68beea44-7863-4e08-97c9-526ada9d0300\r\nCorrelation ID: 064a85fe-6d37-43bd-ae59-3dca04232188\r\nTimestamp: 2022-06-24 09:08:56Z",
"error_codes": [
900144
],
"timestamp": "2022-06-24 09:08:56Z",
"trace_id": "68beea44-7863-4e08-97c9-526ada9d0300",
"correlation_id": "064a85fe-6d37-43bd-ae59-3dca04232188",
"error_uri": "https://login.microsoftonline.com/error?code=900144"
}
I can get access token by your request
But this doesn't mean you can get access token by simulating a post request in your code, Microsoft require users to use Msal library to authenticate and get access token. For example, with client credential flow to get access token:
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "your_azuread_clientid";
var clientSecret = "corresponding_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
If you used msal sdk to authenticate your app, you can also use graph sdk to call graph api:
using Azure.Identity;
using Microsoft.Graph;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "hanxia.onmicrosoft.com";
var clientId = "azure_ad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var temp = await graphClient.Users.Request().GetAsync();
Try using application/json for the Content-Type header of your request instead of application/x-www-form-urlencoded.
In the Postman ensure that checkbox x-www-urlencoded is selected on tab Body.
Looks like you are sending data in json format.
You create a request model:
public class ReqModel
{
public string client_id{ get; set; }
public string scope{ get; set; }
public string client_secret{ get; set; }
public string grant_type{ get; set; }
}
Then you try to create postData like this:
ReqModel reqModel = new()
{
client_id = "65577dd2-cc76-46af-a1ac-71582eac6af2",
scope = "https://graph.microsoft.com/.default",
client_secret = "my_client_secret",
grant_type = "client_credentials"
};
string postData = JsonConvert.SerializeObject(reqModel, Newtonsoft.Json.Formatting.Indented);
I'm trying to create a version in JIRA for a specific project.
I'm able to do the process via Postman by building my requests manually, but it fails with a 404 when creating the version record via .NET.
I'm assuming .NET adds pesky parameters to the request that Postman doesn't do.
The weird thing is that the authentication call works, but the the version creation fails.
Here's the helper I wrote:
public class JIRA
{
private string AuthToken { get; set; }
private const string c_JIRAUrl = "https://org.atlassian.net";
private const string c_LoginUrl = c_JIRAUrl + "/rest/auth/1/session";
private const string c_CreateVersionUrl = c_JIRAUrl + "/rest/api/2/version";
public JIRA()
{
//this works...
var authResponse = ExecuteRequest(c_LoginUrl, "POST", new
{
username = "login",
password = "password"
});
AuthToken = authResponse["session"]["value"].ToString();
}
public void CreateVersion(string name, string projectKey, ProjectEnvironment environment)
{
//lets hardcode the same data I use in Postman for testing purposes...
var createVersionResponse = ExecuteRequest(c_CreateVersionUrl, "POST", new
{
description = "An excellent version",
name = "1.1.2",
archived = false,
released = false,
project = "TEST"
});
}
private JObject ExecuteRequest(string url, string method, object data)
{
HttpWebResponse response;
var jsonDataString = JsonConvert.SerializeObject(data);
byte[] dataBytes = Encoding.Default.GetBytes(jsonDataString);
var responseText = string.Empty;
var wr = (HttpWebRequest)WebRequest.Create(url);
wr.ContentType = "application/json";
if (!string.IsNullOrEmpty(AuthToken))
wr.Headers.Add(HttpRequestHeader.Authorization, $"Bearer {AuthToken}");
wr.Method = method;
wr.ContentLength = dataBytes.Length;
wr.Accept = "application/json";
using (var webStream = wr.GetRequestStream())
{
webStream.Write(dataBytes, 0, dataBytes.Length);
response = (HttpWebResponse)wr.GetResponse();
}
using (var sr = new StreamReader(response.GetResponseStream()))
{
responseText = sr.ReadToEnd();
}
return JObject.Parse(responseText);
}
}
The CreateVersion method always fails with a 404.
As I've said, doing the same (retrieving the token, creating the version) all works in Postman.
Any ideas what's going on ?
Thanks.
Apparently, when retrieving the token (/rest/auth/1/session) the response contains cookies that POSTMAN was sending back in the 2nd request (creating the version). I had to fire up Fiddler to find out it was doing so because its UI was not saying so.
My .NET client was not doing so. When making it do so, it works.
I'm a little miffed that a REST service expects cookies...
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.
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
creating a POST request.
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)
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/
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;
}
}
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;
}
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();
}
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.
GET call for Authorization Code
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...
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)
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.
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; }
}
}
I've written below C# code to login to JIRA Rest API:
var url = new Uri("http://localhost:8090/rest/auth/latest/session?os_username=tempusername&os_password=temppwd");
var request = WebRequest.Create(url) as HttpWebRequest;
if (null == request)
{
return "";
}
request.Method = "POST";
request.ContentType = "application/json";
request.ContentLength = 200;
request.KeepAlive = false;
using (var response = request.GetResponse() as HttpWebResponse)
{
}
When I execute this, application just goes on running without returning any response. Please suggest if this is the right way of calling JIRA Login using REST API
For basic authentication you need to send in the username and password in a base64-encoding. Guidelines can be found in the API examples on atlassians developer page:
https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Basic+Authentication
, if you are doing it in C# you need to send the encoded data in the header in the following format:
"Authorization: Basic [ENCODED CREDENTIALS]"
Here is a simple example:
public enum JiraResource
{
project
}
protected string RunQuery(
JiraResource resource,
string argument = null,
string data = null,
string method = "GET")
{
string url = string.Format("{0}{1}/", m_BaseUrl, resource.ToString());
if (argument != null)
{
url = string.Format("{0}{1}/", url, argument);
}
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.ContentType = "application/json";
request.Method = method;
if (data != null)
{
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(data);
}
}
string base64Credentials = GetEncodedCredentials();
request.Headers.Add("Authorization", "Basic " + base64Credentials);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
string result = string.Empty;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
result = reader.ReadToEnd();
}
return result;
}
private string GetEncodedCredentials()
{
string mergedCredentials = string.Format("{0}:{1}", m_Username, m_Password);
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
return Convert.ToBase64String(byteCredentials);
}
(JiraResource is just an enum I use to decide which part of the API to use)
I hope this will help!
Here is a simpler solution which works as required:
var mergedCredentials = string.Format("{0}:{1}", username, password);
var byteCredentials = Encoding.UTF8.GetBytes(mergedCredentials);
var encodedCredentials = Convert.ToBase64String(byteCredentials);
using (WebClient webClient = new WebClient())
{
webClient.Headers.Set("Authorization", "Basic " + encodedCredentials);
return webClient.DownloadString(url);
}
If you don't want to encode your credentials in every request here is how to do it using cookies.
When requesting the cookie you don't need to add any authorization on the headers. This method will accept a JSON string with the user name and password and the URL. It will return the cookie values.
public async Task<JiraCookie> GetCookieAsync(string myJsonUserNamePassword, string JiraCookieEndpointUrl)
{
using (var client = new HttpClient())
{
var response = await client.PostAsync(
JiraCookieEndpointUrl,
new StringContent(myJsonUserNamePassword, Encoding.UTF8, "application/json"));
var json = response.Content.ReadAsStringAsync().Result;
var jiraCookie= JsonConvert.DeserializeObject<JiraCookie>(json);
return jArr;
}
}
public class JiraCookie
{
public Session session { get; set; }
}
public class Session
{
public string name { get; set; }
public string value { get; set; }
}
When I call it using url: http://[baseJiraUrl]/rest/auth/1/session it returns the following JSON response:
{
"session" : -{
"name" : JSESSIONID,
"value" : cookieValue
}
Keep in mind the URL above is valid in the version of JIRA I'm using and may vary depending on which version you're using. Read the JIRA API documentation for the correct URL for the version you are using. I'm using the following:
https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#auth/1/session
Remember you'll have to store your cookie and use it on every subsequent request.
Check out this answer on how add cookies to your HttpClient request: How do I set a cookie on HttpClient's HttpRequestMessage.
Once you're done with the cookie (logging out) simply send a delete http request with the same URL as the post.
I tweaked the RunQuery code so that it will run today (Apr 2018). The encrypt/decrypt referenced below is from the following link (I converted it to an extension method and threw values into environment).
https://stackoverflow.com/questions/10168240/encrypting-decrypting-a-string-in-c-sharp
I successfully execute the code from LinqPad - thus the Dump() command after RunQuery
private string _baseUrl = "https://xxxxxx.atlassian.net";
private string _username = "YourLogin";
void Main()
{
RunQuery(JiraResource.project).JsonToXml().Dump();
}
public enum JiraResource { project }
private const string restApiVersion = "/rest/api/2/";
protected string RunQuery( JiraResource resource, string argument = null, string data = null, string method = "GET")
{
string url = $"{_baseUrl}{restApiVersion}{resource}";
if (argument != null) url = $"{url}{argument}/";
var request = WebRequest.Create(url) as HttpWebRequest;
request.ContentType = "application/json";
request.Method = method;
if (data != null)
{
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(data);
}
}
string base64Credentials = GetEncodedCredentials();
request.Headers.Add("Authorization", "Basic " + base64Credentials);
var response = request.GetResponse() as HttpWebResponse;
string result = string.Empty;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
result = reader.ReadToEnd();
}
return result;
}
private string GetEncodedCredentials()
{
var encryptedPassword = Environment.GetEnvironmentVariable("PassEncrypted");
var encryptionSalt = Environment.GetEnvironmentVariable("PassSalt");
var password = encryptedPassword.Decrypt(encryptionSalt);
var mergedCredentials = $"{_username}:{password}";
var byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
return Convert.ToBase64String(byteCredentials);
}
public static class MyExtensions
{
public static XElement JsonToXml(this string jsonData, bool isAddingHeader = true)
{
var data = isAddingHeader
? "{\"record\":" + jsonData + "}"
: jsonData;
data = data // Complains if xml element name starts numeric
.Replace("16x16", "n16x16")
.Replace("24x24", "n24x24")
.Replace("32x32", "n32x32")
.Replace("48x48", "n48x48");
var result = JsonConvert.DeserializeXmlNode(data, "data");
var xmlResult = XElement.Parse(result.OuterXml);
return xmlResult;
}
}
For posting multipart content in Rest I use Tiny.RestClient.
var client = new TinyRestClient(new HttpClient(), "http://localhost:8090");
var strResult = await client.PostRequest("rest/auth/latest/session).
WithBasicAuthentication("username", "password")
ExecuteAsStringAsync();
static void Main(string[] args)
{
using (WebClient wc = new WebClient())
{
wc.Headers.Add("Authorization", "Basic " + GetEncodedCredentials());
string tasks = wc.DownloadString("yourjiraurl/search?jql=task=bug");
var taskdetails = JsonConvert.DeserializeObject<TaskDetails>(tasks);
}
}
static string GetEncodedCredentials()
{
string mergedCredentials = string.Format("{0}:{1}", "UserName", "Password");
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
return Convert.ToBase64String(byteCredentials);
}
I was trying to log in to my WordPress admin panel using C# (WebClient/HttpWebRequest).
I send a POST request to /wp-login.php.
It responds with cookies like this:
Set-Cookie: wordpress_26...cf9e; path=/wordpress/wp-content/plugins; httponly
Set-Cookie: wordpress_26...cf9e; path=/wordpress/wp-admin; httponly
Set-Cookie: wordpress_logged_in_26...43b3; path=/wordpress/; httponly
And it also redirects to /wp-admin/: Location: http://109.120.169.99/wordpress/wp-admin/
The problem is that the second cookie (with path=/wp-admin) is not added to CookieContainer, so login fails.
I looked into HttpWebRequest source code and http://referencesource.microsoft.com/#System/net/System/Net/_CookieModule.cs (OnReceiveHeaders) and found out that it uses ResponseUri for all cookies.
I tried to add the cookie manually using response uri and got an exception that path is invalid. After that I found this answer CookieException with CookieContainer: The 'Path' part of the cookie is invalid : it says that the Uri has to match the path from the cookie when adding to CookieContainer.
I ended up disabling AllowAutoRedict (handling it manually) and manually adding these cookies.
Here is my code:
public static void Auth(string url, string username, string password)
{
var webClient = new CookieAwareWebClient();
// load page to get some cookies
string loginPageHtml = webClient.DownloadString(url);
webClient.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
webClient.Headers.Add(HttpRequestHeader.Referer, url);
var postValues = new NameValueCollection()
{
{"log", username},
{"pwd", password},
{"wp-sumbit", "Log In"},
{"redirect_to", url.Replace("wp-login.php", "wp-admin/") },
{"testcookie", "1"}
};
webClient.AllowRedirects = false; // to handle cookies and redirect manually
byte[] response = webClient.UploadValues(url, postValues);
string html = Encoding.UTF8.GetString(response);
// <FIX>, handle cookies and redirect manually
string cookies = webClient.ResponseHeaders[HttpResponseHeader.SetCookie];
var cookiesArr = cookies.Split(',');
foreach (var cookieStr in cookiesArr)
{
var parts = cookieStr.Split(';').Select(s => s.Trim());
foreach (var part in parts)
{
if (part.Contains("path="))
{
string path = part.Replace("path=", "");
var uri = new Uri(url);
if (path != "/" && !uri.LocalPath.Contains(path))
{
var uriBuilder = new UriBuilder(uri.Scheme, uri.Host, 80,
path);
webClient.CookieContainer.SetCookies(uriBuilder.Uri, cookieStr);
}
}
}
}
// </FIX>
while (webClient.StatusCode() == HttpStatusCode.Redirect)
{
string redirectUrl = webClient.ResponseHeaders[HttpResponseHeader.Location];
html = webClient.DownloadString(redirectUrl);
}
if (html.Contains("?action=logout"))
Console.WriteLine("Login success");
else
Console.WriteLine("Login fail");
}
WebClient:
// WebClient with CookieContainer
public class CookieAwareWebClient : WebClient
{
private WebRequest _request = null;
private WebResponse _response = null;
public CookieContainer CookieContainer { get; private set; }
public string UserAgent { get; set; }
public bool AllowRedirects { get; set; }
public CookieAwareWebClient()
{
UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36";
CookieContainer = new CookieContainer();
AllowRedirects = true;
}
protected override WebRequest GetWebRequest(Uri address)
{
_request = base.GetWebRequest(address);
if (_request is HttpWebRequest)
{
var httpRequest = (HttpWebRequest)_request;
httpRequest.CookieContainer = CookieContainer;
httpRequest.UserAgent = UserAgent;
httpRequest.AllowAutoRedirect = AllowRedirects;
httpRequest.ProtocolVersion = HttpVersion.Version11;
httpRequest.Timeout = 50000;
}
return _request;
}
protected override WebResponse GetWebResponse(WebRequest request)
{
_response = base.GetWebResponse(request);
return _response;
}
// Returns status code of the last response
public HttpStatusCode StatusCode()
{
var httpResponse = _response as HttpWebResponse;
if (httpResponse == null)
throw new InvalidOperationException("Unable to retrieve the status code, maybe you have not made a request yet.");
var result = httpResponse.StatusCode;
return result;
}
}
I also tried using .NET 4.5 HttpClient but the same result (I think it uses HttpWebRequest underneath too):
public static void Auth2(string url, string username, string password)
{
using (var httpClient = new HttpClient())
{
var loginPageHtml = httpClient.GetStringAsync(url).Result;
var postContent = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("log", username),
new KeyValuePair<string, string>("pwd", password),
new KeyValuePair<string, string>("wp-submit", "Log In"),
new KeyValuePair<string, string>("redirect_to", url.Replace("wp-login.php", "wp-admin/")),
new KeyValuePair<string, string>("testcookie", "1")
};
var response = httpClient.PostAsync(url, new FormUrlEncodedContent(postContent)).Result;
string html = response.Content.ReadAsStringAsync().Result;
if (html.Contains("?action=logout"))
Console.WriteLine("Login success");
else
Console.WriteLine("Login fail");
}
}
Am I doing something wrong or is it a bug in HttpWebRequest/CookieContainer?
Here is the full source code for convenience if someone wants to test it: https://gist.github.com/AlexP11223/5c176972426605ee2112 (my test website and login/password also should work)
And Fiddler logs:
http://1drv.ms/1qyAgGi — webbrowser (Chrome), it adds this cookie
http://1drv.ms/1kqsLDI — WebClient
http://1drv.ms/1kqsC2Y —
HttpClient