YouTube Data API: Just started giving back 403 forbidden errors when trying to update videos even though OAuth is set up correctly - youtube-api

I am receiving the following error when trying to update video snippets using YouTube API using C#:
Google.GoogleApiException: Google.Apis.Requests.RequestError
Forbidden [403]
Errors [
Message[Forbidden] Location[ - ] Reason[forbidden] Domain[youtube.video]
]
However, I can read videos just fine.
I'm using oauth and have granted proper access to my app. I've tried recreating my oauth credentials and re-granting access but to no avail.
This is something that was working previously. No change to my code. No change to my channel or videos. I've also verified no quota limits exceeded for the day.
The code:
private async Task<UserCredential> GetCredentialAsync()
{
UserCredential credential;
using (var stream = new FileStream(<<path to json file containing my oauth credentials>>, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeForceSsl, YouTubeService.Scope.Youtube, YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None,
new FileDataStore(appName)
);
}
return credential;
}
private async Task<bool> UpdateSingleVideoAsync(string id, VideoSnippet snippet)
{
var credential = this.GetCredentialAsync().Result;
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var video = new Video();
video.Id = id;
video.Snippet = snippet;
var request = youtubeService.Videos.Update(video, "snippet");
var response = await request.ExecuteAsync();
return true;
}
Any pointers on getting more data about the problem?

Related

Authorized Web API Access from MVC

I am facing some issue with Azure AD authentication.
My application architecture is Asp.net MVC Web & Web API as middle ware
when i am trying to authenticate using AD Token at web API from MVC, i am not able to get any error and even no response from WEB API in Code
But if i try accessing the API using browser where i have already used credentials for Authenticating to MVC app it works fine.
Below is the code to access API but it didn't worked
AuthenticationResult result = null;
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string ApiClientId = ConfigurationManager.AppSettings["ida:ApiClientId"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:ApplicationURI"];
string postLoginRedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
string authority = aadInstance + tenantId;
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "openid profile offline_access email User.Read" };
try
{
// try to get an already cached token
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException ex)
{
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
//Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
// Build the auth code request Uri
string authReqUrl = await OAuth2RequestManager.GenerateAuthorizationRequestUrl(scopes, app, this.HttpContext, Url);
}
catch (MsalException msalex)
{
Response.Write($"Error Acquiring Token:{System.Environment.NewLine}{msalex}");
}
}
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
HttpClient client = new HttpClient(handler);
//apiUrl client.BaseAddress = apiUrl;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiUrl + "api/General/GetUserDetailsByEmailAddress?emailAddress=ikhlesh.saxena#amexassetmanagement.com");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
try
{
HttpResponseMessage response = await client.SendAsync(request);
}
catch (Exception ex)
{
}
Check if your scopes allow you to access your API. Also, you need to debug on the API side whether the token is coming with a request, and if yes how it's validated.

using Microsoft graph API i want user profile photo

Hi I am trying to get user photo, used
var tenantId = configuration.GetSection("AzureAd").GetSection("TenantId").Value;
var clientId = configuration.GetSection("AzureAd").GetSection("ClientId").Value;
var clientSecret = configuration.GetSection("AzureAd").GetSection("clientSecret").Value;
var InviteRedirectUrl = configuration.GetSection("AzureAd").GetSection("InviteRedirectUrl").Value;
var Instance = configuration.GetSection("AzureAd").GetSection("Instance").Value;
var URL = Instance + tenantId + "/v2.0";
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(URL)
.WithClientSecret(clientSecret)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
Stream photo = await graphServiceClient.Me.Photo.Content.Request().GetAsync();
I got following error
Code: BadRequest Message: Current authenticated context is not valid for this request. This occurs when a request is made to an endpoint that requires user sign-in. For example, /me requires a signed-in user. Acquire a token on behalf of a user to make requests to these endpoints. Use the OAuth 2.0 authorization code flow for mobile and native apps and the OAuth 2.0 implicit flow for single-page web apps. Inner error:
how to solve it?

Sign In using raw HttpRequestMessage in ASP.NET MVC

I have been testing some code to sign in users to their Microsoft/school/work accounts using raw HttpRequestMessage and HttpResponseMessage. I know there are libraries available to do this but I want to test the raw approach as well (especially usage of refresh tokens), while looking for the right library to handle it.
I'm currently learning authentication, with limited knowledge of ASP.NET/Core.
I'm following this guide: https://learn.microsoft.com/en-us/graph/auth-v2-user
I've just modified the SignIn() method in AccountController in an example project that used more high level libraries to sign in.
I'm requesting an authorization code.
The SignIn() code:
public void SignIn()
{
using (var httpClient = new HttpClient())
{
try
{
var tenant = "my tenant id";
var clientId = ConfigurationManager.AppSettings["ida:AppID"];
var responseType = "id_token+code";
var redirectURI = ConfigurationManager.AppSettings["ida:RedirectUri"];
var responseMode = "form_post";//query";
var appScopes = ConfigurationManager.AppSettings["ida:AppScopes"];
var scopes = $"openid profile offline_access {appScopes}";
var state = "12345";
//var prompt = "consent";
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize", tenant);
var body = string.Format("client_id={1}&response_type={2}&redirect_uri={3}&response_mode={4}&scope={5}&state={6}", tenant, clientId, responseType, redirectURI, responseMode, scopes, state);
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead).Result;
var content = response.Content.ReadAsStringAsync().Result;
}
catch (Exception ex)
{
}
}
//if (!Request.IsAuthenticated)
//{
// // Signal OWIN to send an authorization request to Azure
// Request.GetOwinContext().Authentication.Challenge(
// new AuthenticationProperties { RedirectUri = "/" },
// OpenIdConnectAuthenticationDefaults.AuthenticationType);
//}
}
I'm just returning void from the method now because I'm not sure what I should return yet.
Debugging and looking at the response variable, the status code is 200, and has some other information to it. However, the content of the HttpResponseMessage, when I paste it into a file and opening it in a browser, displays (or redirects to) https://login.microsoftonline.com/cookiesdisabled, which shows a message saying that I could not be logged in because my browser blocks cookies. However, I don't think this really is the case.
How can I resolve this and have the user log in and consent, and get the authorization code?
I couldn't really find any example in ASP.NET that uses this raw approach. Is it not recommended?
You should fistly understand how OAuth 2.0 authorization code flow works in Azure AD V2.0 :
Microsoft identity platform and OAuth 2.0 authorization code flow
The general process would be like :
When login in client application, user will be redirect to Azure AD login endpoint(https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize) and provides info like which client(client_id) in which tenant(tenant id) user wants to login , and redirect back to which url(redirect_uri) after successful login.
User enter credential , Azure AD validate credential and issue code and redirect user back to redirect url provided in step 1 (Also match one of the redirect_uris you registered in the portal).
The client application will get the code and send http post request with code to acquire access token .
So if you want to manally implement the code flow in your application , you can refer to below code sample :
public async Task<IActionResult> Login()
{
string authorizationUrl = string.Format(
"https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize?response_type=code&client_id={1}&redirect_uri={2}&scope={3}",
"tenantID", "ClientID", "https://localhost:44360/Home/CatchCode",
"openid offline_access https://graph.microsoft.com/user.read");
return Redirect(authorizationUrl);
}
private static readonly HttpClient client = new HttpClient();
public async Task<ActionResult> CatchCode(string code)
{
var values = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", "XXXXXX"},
{ "code", code},
{ "redirect_uri", "https://localhost:44360/Home/CatchCode"},
{ "scope", "https://graph.microsoft.com/user.read"},
{ "client_secret", "XXXXXXXXXXX"},
};
var content = new FormUrlEncodedContent(values);
//POST the object to the specified URI
var response = await client.PostAsync("https://login.microsoftonline.com/cb1c3f2e-a2dd-4fde-bf8f-f75ab18b21ac/oauth2/v2.0/token", content);
//Read back the answer from server
var responseString = await response.Content.ReadAsStringAsync();
//you can deserialize an Object use Json.NET to get tokens
}
That just is simple code sample which will get Microsoft Graph's access token , you still need to care about url encode and catch exception , but it shows how code flow works .

Store authentification data in MVC

I have created a custom Authorize attribute where I use the Office Graph to get AAD groups the current user is member of, and based on those I reject or authorize the user. I want to save the groups, because the call to Office Graph takes some performance. What would be the correct way to save that kind of data? I can see some people saves it to a SQL server, but then I would need to ensure cleanup etc.
Also I can see in some threads the session state is stated to be a bad choice due to concurrency. So the question is what options do you have to store this kind of information?
All suggestions are welcome.
If you were only using the group_id info, there is no need to use Office Graph and store it at all. We can enable Azure AD issue the groups claims by change the manifest of Azure AD like below:(refer this code sample)
"groupMembershipClaims": "All",
And if you are also using other info about groups, you can store these info into claims. Here is a code sample that add the name of groups into claims for your reference:
AuthorizationCodeReceived = async context =>
{
ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(Globals.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigHelper.Authority, new TokenDbCache(userObjectId));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);
ActiveDirectoryClient graphClient = new ActiveDirectoryClient(new Uri(ConfigHelper.GraphServiceRoot),
async () => { return await Task.FromResult(result.AccessToken); }
);
try
{
foreach (var groupClaim in context.AuthenticationTicket.Identity.FindAll("groups"))
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri($"https://graph.windows.net/adfei.onmicrosoft.com/groups/{groupClaim.Value}?api-version=1.6"),
Method = HttpMethod.Get,
};
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage httpResponse = httpClient.SendAsync(request).Result;
var retJSON = httpResponse.Content.ReadAsStringAsync().Result;
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(retJSON);
((ClaimsIdentity)context.AuthenticationTicket.Identity).AddClaim(new Claim("groupName", dict["displayName"].ToString()));
}
}
}
catch (Exception ex)
{
}
},
Then we can these info from controller using the code below:
ClaimsPrincipal.Current.FindAll("groupName")

Why am I getting an Access forbidden error when using the youtube api?

When uploading videos to YouTube using the YouTube api v3, I get the following error:
Google.Apis.Requests.RequestError
Access forbidden. The request may not be properly authorized. [403]
Errors [
Message[Access forbidden. The request may not be properly authorized.] Location[ - ] Reason[forbidden] Domain[youtube.common]
]
The app will successfully upload a couple videos, then randomly throws this 403 error.
The quota limit is 1 millon queries per day, I have only used about 1%.
There are approximately 1200 videos already on the channel that were uploaded using the same app, now all of a sudden in the last few days I am getting this error. I've tried uploading videos to another channel and had no problems, so it looks as though the issue is specific to this one channel. Seems as though it's being throttled, but I don't know why?
I have not contravened any terms of service, the videos are original, the account is in good standing and I'm within the quota limit.
If there are any other limits, why does Google not document it in the api refrence? That way we can design our apps according to policy.
It makes it impossible to build an app if you follow spec and then Google have a different set of rules on the backend and you are left guessing what you are and aren't allowed to do.
Using this code:
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
namespace Google.Apis.YouTube.Samples
{
/// <summary>
/// YouTube Data API v3 sample: upload a video.
/// Relies on the Google APIs Client Library for .NET, v1.7.0 or higher.
/// See https://developers.google.com/api-client-library/dotnet/get_started
/// </summary>
internal class UploadVideo
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("YouTube Data API: Upload Video");
Console.WriteLine("==============================");
try
{
new UploadVideo().Run().Wait();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine("Error: " + e.Message);
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
private async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"REPLACE_ME.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}

Resources