Executing POST request for Microsoft Graph API to add members to an AD group - c#

I am trying to add members to an AD groups invoking Microsoft Graph API through an Azure Function
It is very easy and straightforward to execute GET requests through Graph API's, but I can't find any examples how I could execute post requests for the Graph API
I do have an example of a post request for the Graph API which is
POST https://graph.microsoft.com/v1.0/groups/{id}/members/$ref
Content-type: application/json
Content-length: 30
{
"#odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{id}"
}
Here is the code I successfully use to retrieve the Graph response
public static async Task<HttpResponseMessage> GetDirectoryUsers(string graphToken, TraceWriter log, string displayName)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", graphToken);
string requestUrl = "https://graph.microsoft.com/v1.0/groups?$top=2&$filter=displayName eq '" + displayName + "'&$expand=Members";
var request = new HttpRequestMessage(new HttpMethod("GET"), requestUrl);
var response = await client.SendAsync(request);
return response;
}
However, I am completely lost how I could execute the request through a C# code within the Azure function to ADD the retrieved users to another AD. How can construct the request URL? How should I handle the odata id within that request URL?
If anyone could help me in any way, I would greatly appreciate it

A reuse method for add sub-group/member to group(O365 doesn't support add sub-group to group now)
/// <param name="graphClient"></param>
/// <param name="groupId"></param>
/// <param name="memberId">memberId/sub-group id</param>
/// <returns></returns>
public static async Task AddGroupMember1(GraphServiceClient
graphClient, string groupId, string memberId)
{
User memberToAdd = new User { Id = memberId };
//Group memberToAdd= new Group { Id = memberId };
await graphClient.Groups[groupId].Members.References.Request().AddAsync(memberToAdd);
}

Here is the answer that worked for me
public static async Task<string> AddGroupMember(string accessToken, string groupId, string memberId)
{
var status = string.Empty;
try
{
string endpoint = "https://graph.microsoft.com/v1.0/groups/" + groupId + "/members/$ref";
string queryParameter = "";
// pass body data
var keyOdataId = "#odata.id";
var valueODataId = "https://graph.microsoft.com/v1.0/directoryObjects/" + memberId;
var values = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(keyOdataId, valueODataId)
};
var jsonData = $#"{{ ""{keyOdataId}"": ""{valueODataId}"" }}";
var body = new StringContent(jsonData, Encoding.UTF8, "application/json");
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Post, endpoint + queryParameter))
{
request.Content = body;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var response = await client.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.NoContent)
status = "Member added to Group";
else
status = $"Unable to add Member to Group: {response.StatusCode}";
}
}
}
}
catch (Exception ex)
{
status = $"Error adding Member to Group: {ex.Message}";
}
return status;
}

I'm using graph api for sending email. The code in below
public async Task<dynamic> SendMail(string accessToken, MailWrapper mail)
{
try
{
GraphServiceClient graphClient = SDKHelper.GetMicrosoftAuthenticatedClient(accessToken);
Message message = await BuildEmailMessage(graphClient, mail);
await graphClient.Me.SendMail(message, true).Request().PostAsync(CancellationToken.None);
var response = await graphClient.Me.MailFolders.SentItems.Messages.Request()
.OrderBy(sendDateTimeDesc)
.Top(1)
.GetAsync();
return await Task.FromResult(response);
}
catch (ServiceException ex)
{
throw ex;
}
}
Assembly Microsoft.Graph, Version=1.9.0.0

That's what worked for me
public void AddUserToGroup(string groupId)
{
var requestUri = $"{_graphApiUrl}/v1.0/groups/{groupId}/members/$ref";
var id = "user_id";
var OdataId = "#odata.id";
var ODataValue = $"https://graph.microsoft.com/v1.0/users/{id}";
var content = $#"{{ ""{OdataId}"": ""{ODataValue}"" }}";
using (var httpClient = new HttpClient())
using (var httpRequest = CreateHttpRequest(HttpMethod.Post, requestUri, content))
{
var response = httpClient.SendAsync(httpRequest).GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode)
{
var reason = $"Status code: {(int)response.StatusCode}, Reason: {response.StatusCode}";
throw new Exception(reason);
}
}
}
And important thing was when creating a request to use:
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
it didn't work with:
request.Content = new StringContent(content);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");

Related

Microsoft Graph API Returning 401 Despite Having Required Scope & Account Type

We have an API that utilizes a service account (work account type - not a personal account) to do the following:
Using our Service Account, Gets a Token from our Azure AD with scopes: ["user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite"]
Use that token to upload a file to our SharePoint. -> This succeeds
Use that token to Get the UserID from email (UserPrincipalNames map in our AD, so this is not a concern) -> This succeeds
Use that token to Create a new chat/Get the existing chat between the user & our service account (using POST /v1.0/chats) -> This returns 401 Unauthorized
Send the message from service account to user (using POST /v1.0/chats/{chatID returned from step 4}/messages)
The users we are attemping to send these messages to are all in our Azure AD.
We are unsure how the previous requests with this token succeed, and the Create/Get Chat fails with a 401. We have confirmed that the token is correctly being set in the authorization header (we are using the exact same process as the previous requests).
Also, we have these API Permissions set in our Azure AD for this application:
If we use jwt.io to examine the token, we do see the scope is set in the token being set in the Authorization header of the request.
Additionally the audience is set to Graph API:
Here is our code:
using Microsoft.Graph;
using Newtonsoft.Json;
using OurAPI.Helpers.IHelpers;
using OurAPI.Models;
using OurAPI.Repositories.IRepositories;
using System.Text;
namespace OurAPI.Repositories
{
public class TeamsRepository : ITeamsRepository
{
private readonly IConfiguration _config;
private readonly ITokenHelper _tokenHelper;
private readonly IHttpClientFactory _httpClientFactory;
public TeamsRepository(IConfiguration config, ITokenHelper tokenHelper, IHttpClientFactory httpClientFactory)
{
_config = config;
_tokenHelper = tokenHelper;
_httpClientFactory = httpClientFactory;
}
public async Task<object> GeneratePDFAndSendToTeams(TeamsPostRequest request)
{
// Authenticate
string token = await _tokenHelper.GetMicrosoftGraphAccessToken();
// Convert from Base64 to Memory Stream
var file = GeneratePDF(request.Base64);
// Upload PDF to SharePoint
var sharepointFile = await UploadFile(file, request, token);
// Get UserID from email
var user = await GetUser(request.User, token);
// Create a new chat with service account or Retrieve Chat if already exists
var chat = await GetChat(user.Id, token);
// Post to chat between service account and requesting user
var chatMessage = await SendToTeams(sharepointFile, chat, token);
return chat;
}
public async Task<object> GetChats(string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/serviceaccount#domain.com/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chats = JsonConvert.DeserializeObject(contents);
return chats;
}
public async Task<User> GetUser(string userEmail, string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/" + userEmail);
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<User>(contents);
return user;
}
public async Task<Chat> GetChat(string userID, string token)
{
var chatRequest = new Models.ChatRequest(userID);
chatRequest.ChatType = "oneOnOne";
var chatMember = new ChatMember();
chatMember.Type = "#microsoft.graph.aadUserConversationMember";
chatMember.Roles = new List<string>() { "owner" };
chatMember.User = $"https://graph.microsoft.com/beta/users('{userID}')";
var serviceAccount = new ChatMember();
serviceAccount.Type = "#microsoft.graph.aadUserConversationMember";
serviceAccount.Roles = new List<string>() { "owner" };
serviceAccount.User = "https://graph.microsoft.com/beta/users('{OurServiceAccountID}')";
chatRequest.Members = new List<ChatMember> { chatMember, serviceAccount };
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
string content = JsonConvert.SerializeObject(chatRequest);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chat = JsonConvert.DeserializeObject<Chat>(contents);
return chat;
}
// POST /chats/{chat-id}/messages
public async Task<ChatMessage> SendToTeams(DriveItem sharePointFile, Chat chat, string token)
{
var chatMessageRequest = new Models.ChatMessageRequest(sharePointFile);
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats/" + chat.Id + "/messages");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StringContent(JsonConvert.SerializeObject(chatMessageRequest), Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
//response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chatMessageResponse = JsonConvert.DeserializeObject<ChatMessage>(contents);
return chatMessageResponse;
}
public async Task<DriveItem> UploadFile(MemoryStream file, TeamsPostRequest teamsPostRequest, string token)
{
string siteID = "{ourSiteID}";
string PTparentID = "{ourParentID}"
string custNameForFile = teamsPostRequest.CustomerName;
using var request = new HttpRequestMessage(HttpMethod.Put, _config["Graph:BaseUrl"] + "/sites/" + siteID + "/drive/items/" + PTparentID + ":/" + custNameForFile + teamsPostRequest.PickTicketNo + ".pdf:/content");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StreamContent(file);
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var driveItem = JsonConvert.DeserializeObject<DriveItem>(contents);
return driveItem;
}
private static MemoryStream GeneratePDF(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
MemoryStream ms = new(bytes);
return ms;
}
}
}
Here is the request we are making to get the token:
public async Task<string> GetMicrosoftGraphAccessToken()
{
string authority = "https://login.microsoftonline.com/{ourTenantID}/";
string[] scopes = new string[] { "user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite" };
IPublicClientApplication app = PublicClientApplicationBuilder.Create(_config["AzureAD:ClientId"])
.WithAuthority(authority)
.Build();
var securePassword = new SecureString();
foreach (char c in _config["MicrosoftGraph:Password"])
{
securePassword.AppendChar(c);
}
AuthenticationResult result = await app.AcquireTokenByUsernamePassword(scopes, _config["MicrosoftGraph:Username"], securePassword)
.ExecuteAsync();
return result.AccessToken;
}
We are at a lose for what could be going on here. Any ideas would be greatly appreciated!
It helps to see the details of the token.
Copy the token, paste it at https://jwt.ms and inspect the details.
You’re probably interested in the scopes.
I guess the account you’re using should be one of the participants of the chat, this cannot be used to create chats between other users.
https://learn.microsoft.com/en-us/graph/api/chat-post you seem to be doing the right request.

Unable to send string as HTTPContent

I have created an API, which shall have the capability to connect to en external API via POST and with a request body in form of a string.
I am able to connect directly to the API from Postman without trouble.. But it does not work via my own API.
Any ideas?
This is the Pastebin.
private string EncodeExternalApiLink = "https://blabla.dk";
private string EncodeExternalApiLinkPostFilter = "searchstring/blabla/api/search";
[HttpPost("getdata/filtered")]
public async Task<IActionResult> GetDataFromExternalFiltered([FromBody] string filter)
{
var filterString = new StringContent(filter);
EncodeExternalToken token = GetExternalToken().Result;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(EncodeExternalApiLink);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.access_token);
using (var response = await client.PostAsync(EncodeExternalApiLinkPostFilter, filterString))
{
return Json(response);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
return Content(content, "application/json");
}
else
{
return NotFound();
}
}
}
}
Salutations. You might need to add a "/" to the end of your base address EncodeExternalApiLink or to the beginning of EncodeExternalApiLinkPostFilter.

Getting Tweets in MVC

I am trying to load tweets but my code isn't working when using my MVC app. However, the exact code works when using a console.
I am asuming it is something to do with "async", I have not had experience using this before and I am unable to find a solution. Break points after "GetAccessToken()" are not hit
In my Controler:
public ActionResult Tweets(int tweets, string username)
{
var twitter = new Twitter();
var userTweets = twitter.GetTweets("chrishall9521", 10).Result.ToList();
this.ViewBag.Tweets = userTweets;
return View();
}
In my "Twitter" class:
public async Task<IEnumerable<string>> GetTweets(string userName, int count)
{
var accessToken = await GetAccessToken();
var requestUserTimeline = new HttpRequestMessage(HttpMethod.Get, string.Format("https://api.twitter.com/1.1/statuses/user_timeline.json?count={0}&screen_name={1}&trim_user=1&exclude_replies=1", count, userName));
requestUserTimeline.Headers.Add("Authorization", "Bearer " + accessToken);
var httpClient = new HttpClient();
HttpResponseMessage responseUserTimeLine = await httpClient.SendAsync(requestUserTimeline);
var serializer = new JavaScriptSerializer();
dynamic json = serializer.Deserialize<object>(await responseUserTimeLine.Content.ReadAsStringAsync());
var enumerableTwitts = (json as IEnumerable<dynamic>);
if (enumerableTwitts == null)
{
return null;
}
return enumerableTwitts.Select(t => (string)(t["text"].ToString()));
}
public async Task<string> GetAccessToken()
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.twitter.com/oauth2/token ");
var customerInfo = Convert.ToBase64String(new UTF8Encoding().GetBytes(OAuthConsumerKey + ":" + OAuthConsumerSecret));
request.Headers.Add("Authorization", "Basic " + customerInfo);
request.Content = new StringContent("grant_type=client_credentials", Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
string json = await response.Content.ReadAsStringAsync();
var serializer = new JavaScriptSerializer();
dynamic item = serializer.Deserialize<object>(json);
return item["access_token"];
}
Don't use .Result like that - you're deadlocking. Use await in the Tweets method instead and make it async. The basic "golden rule" for using async is that when you use it, you use it all the way up the chain.
Here's a blog post with similar examples for further reading.

How to post JSON with HttpClient using C#?

I have no idea how to POST JSON with HttpClient. I find some solution, like this, but I have to use HttpClient, cause of async and have to add a header.
This is my code below. Any idea how to fix it?
List<Order> list = new List<Order> { new Order() { Name = "CreatedTime", OrderBy = 1 } };
Queues items = new Queues { Orders = list };
var values = new Dictionary<string, string> { { "Orders", JsonConvert.SerializeObject(list) } };
var content = new FormUrlEncodedContent(values);
//HttpContent cc = new StringContent(JsonConvert.SerializeObject(items));
_msg = await _client.PostAsync(input, content);
//_msg = await _client.PostAsync(input, cc);
var response = await _msg.Content.ReadAsStringAsync();
You can use the method PostAsJsonAsync which can be found in the extensions assemblies:
System.Net.Http.Formatting.dll
Example
public static async Task SendJsonDemo(object content)
{
using(var client = new HttpClient())
{
var response = await client.PostAsJsonAsync("https://example.com", content);
}
}
If you want to add custom headers to the request, add it to DefaultRequestHeaders:
client.DefaultRequestHeaders.Add("mycustom", "header1");
You can send any type of request like as
public static async Task<HttpResponseMessage> SendRequest(HttpMethod method, string endPoint, string accessToken, dynamic content = null)
{
HttpResponseMessage response = null;
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(method, endPoint))
{
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
if (content != null)
{
string c;
if (content is string)
c = content;
else
c = JsonConvert.SerializeObject(content);
request.Content = new StringContent(c, Encoding.UTF8, "application/json");
}
response = await client.SendAsync(request).ConfigureAwait(false);
}
}
return response;
}

Using a token to search on Twitter with OAuth2

Before Twitter switched to OAuth2, I was using the following query:
string atomTweetSearchURL = string.Format("http://search.twitter.com/search.atom?q={0}", searchText);
This no longer works, so now I'm trying to switch to OAuth2. I manage to successfully retrieve a token, but once I've got this, I seem to be unable to actually perform the search. Here's the latest incarnation of what I've tried:
var searchUrl = string.Format("https://api.twitter.com/1.1/search/tweets.json?q={0}&access_token={1}&token_type={2}", srchStr, twitAuthResponse.access_token, twitAuthResponse.token_type);
WebRequest srchRequest = WebRequest.Create(searchUrl);
using (var response2 = await srchRequest.GetResponseAsync())
{
Stream stream = response2.GetResponseStream();
using (StreamReader sr = new StreamReader(stream))
{
string jsonResponse = await sr.ReadToEndAsync();
}
}
This gives me a 400 - bad request.
I've also tried building the request like this:
System.Net.Http.HttpClient srchRequest = new System.Net.Http.HttpClient();
string authHdr = string.Format(srchHeaderFormat, twitAuthResponse.token_type, twitAuthResponse.access_token);
srchRequest.DefaultRequestHeaders.Add("Authorization", authHdr);
There's a massive quantity of articles out there detailing how to do this, but none of them seem to work correctly with WinRT. Can anyone point me in the right direction?
EDIT
Here's my code to get the token:
var oAuthConsumerKey = key;
var oAuthConsumerSecret = secret;
var oAuthUri = new Uri("https://api.twitter.com/oauth2/token");
var authHeaderFormat = "Basic {0}";
var authHeader = string.Format(authHeaderFormat,
Convert.ToBase64String(Encoding.UTF8.GetBytes(Uri.EscapeDataString(oAuthConsumerKey)
+ ":" +
Uri.EscapeDataString((oAuthConsumerSecret)))
));
var req = new HttpClient();
req.DefaultRequestHeaders.Add("Authorization", authHeader);
HttpRequestMessage msg = new HttpRequestMessage(new HttpMethod("POST"), oAuthUri);
msg.Content = new HttpStringContent("grant_type=client_credentials");
msg.Content.Headers.ContentType = new Windows.Web.Http.Headers.HttpMediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await req.SendRequestAsync(msg);
TwitAuthenticateResponse twitAuthResponse;
using (response)
{
string objectText = await response.Content.ReadAsStringAsync();
twitAuthResponse = JSonSerialiserHelper.Deserialize<TwitAuthenticateResponse>(objectText);
}
With the 1.1 API you don't pass the access token as part of the url, you need to include it as the Authorization header as "Bearer access_token" so you were almost there!
EDIT
To do this in the Windows.Web.Http namespace the following works:
private static async Task SearchTweets(AuthenticationResponse twitAuthResponse)
{
string srchStr = "tweet";
var client = new HttpClient();
var searchUrl = string.Format("https://api.twitter.com/1.1/search/tweets.json?q={0}", srchStr);
var uri = new Uri(searchUrl);
client.DefaultRequestHeaders.Authorization = new HttpCredentialsHeaderValue("Bearer", twitAuthResponse.AccessToken);
var response2 = await client.GetAsync(uri);
string content = await response2.Content.ReadAsStringAsync();
}
Or with System.Net.Http use the following:
This code will run the search for srchStr using the access token you already acquired as you showed in the first example:
var client = new HttpClient();
var searchUrl = string.Format("https://api.twitter.com/1.1/search/tweets.json?q={0}", srchStr);
var uri = new Uri(searchUrl);
client.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0}", twitAuthResponse.access_token));
HttpResponseMessage response = await client.GetAsync(uri);
Task<string> content = response.Content.ReadAsStringAsync();
EDIT
This is a strange one, I tested your code and you're right it does throw an exception when attempting to add the Auth header, however the code I had used for grabbing the Access Token is almost identical but uses the System.Net.Http methods rather than the Windows.Web.Http ones that you use and it works, so I'll provide my code here, maybe this is a bug in the framework, or someone else can provide some more insight! This also uses the JSON.NET library which can be found on NuGet.
private static async Task SearchTweets(AuthenticationResponse twitAuthResponse)
{
string srchStr = "tweet";
var client = new HttpClient();
var searchUrl = string.Format("https://api.twitter.com/1.1/search/tweets.json?q={0}", srchStr);
var uri = new Uri(searchUrl);
client.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0}", twitAuthResponse.AccessToken));
HttpResponseMessage response2 = await client.GetAsync(uri);
string content = await response2.Content.ReadAsStringAsync();
}
private async void GetAuthenticationToken()
{
var client = new HttpClient();
var uri = new Uri("https://api.twitter.com/oauth2/token");
var encodedConsumerKey = WebUtility.UrlEncode(TwitterConsumerKey);
var encodedConsumerSecret = WebUtility.UrlEncode(TwitterConsumerSecret);
var combinedKeys = String.Format("{0}:{1}", encodedConsumerKey, encodedConsumerSecret);
var utfBytes = System.Text.Encoding.UTF8.GetBytes(combinedKeys);
var encodedString = Convert.ToBase64String(utfBytes);
client.DefaultRequestHeaders.Add("Authorization", string.Format("Basic {0}", encodedString));
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var postData = new FormUrlEncodedContent(data);
var response = await client.PostAsync(uri, postData);
AuthenticationResponse authenticationResponse;
using (response)
{
if (response.StatusCode != System.Net.HttpStatusCode.OK)
throw new Exception("Did not work!");
var content = await response.Content.ReadAsStringAsync();
authenticationResponse = JsonConvert.DeserializeObject<AuthenticationResponse>(content);
if (authenticationResponse.TokenType != "bearer")
throw new Exception("wrong result type");
}
await SearchTweets(authenticationResponse);
}
}
class AuthenticationResponse
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}

Categories

Resources