I'm trying to get metadata's information about all of a Google Drive.
This is my code :
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Green;
var clientId = "MyID";
var secret = "MySecret";
var service = GoogleDriveFileListDirectoryStructure.AuthenticateOauth(clientId, secret, "AppliUserAutor2");
var allFiles = GoogleDriveFileListDirectoryStructure.ListAll(service, new GoogleDriveFileListDirectoryStructure.FilesListOptionalParms { Q = "('root' in parents)", PageSize = 1000 });
GoogleDriveFileListDirectoryStructure.PrettyPrint(service, allFiles, "");
Console.ReadLine();
}
internal class GoogleDriveFileListDirectoryStructure
{
public static DriveService AuthenticateOauth(string clientId, string clientSecret, string userName)
{
try
{
if (string.IsNullOrEmpty(clientId))
throw new ArgumentNullException("clientId");
if (string.IsNullOrEmpty(clientSecret))
throw new ArgumentNullException("clientSecret");
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
// These are the scopes of permissions you need. It is best to request only what you need and not all of them
string[] scopes = new string[] { DriveService.Scope.DriveReadonly }; //View the files in your Google Drive
var credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = System.IO.Path.Combine(credPath, ".credentials/apiName");
// Requesting Authentication or loading previously stored authentication for userName
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, System.Threading.CancellationToken.None
, new FileDataStore(credPath, true)).Result;
// Returning the SheetsService
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive Oauth2 Authentication Sample"
});
}
catch (Exception ex)
{
Console.WriteLine("Create Oauth2 account DriveService failed" + ex.Message);
throw new Exception("CreateServiceAccountDriveFailed", ex);
}
}
public class FilesListOptionalParms
{
/// The source of files to list.
public string Corpus { get; set; }
/// A comma-separated list of sort keys. Valid keys are 'createdTime', 'folder', 'modifiedByMeTime', 'modifiedTime', 'name', 'quotaBytesUsed', 'recency', 'sharedWithMeTime', 'starred', and 'viewedByMeTime'. Each key sorts ascending by default, but may be reversed with the 'desc' modifier. Example usage: ?orderBy=folder,modifiedTime desc,name. Please note that there is a current limitation for users with approximately one million files in which the requested sort order is ignored.
public string OrderBy { get; set; }
/// The maximum number of files to return per page.
public int PageSize { get; set; }
/// The token for continuing a previous list request on the next page. This should be set to the value of 'nextPageToken' from the previous response.
public string PageToken { get; set; }
/// A query for filtering the file results. See the "Search for Files" guide for supported syntax.
public string Q { get; set; }
/// A comma-separated list of spaces to query within the corpus. Supported values are 'drive', 'appDataFolder' and 'photos'.
public string Spaces { get; set; }
}
public static Google.Apis.Drive.v3.Data.FileList ListAll(DriveService service, FilesListOptionalParms optional = null)
{
try
{
// Initial validation.
if (service == null)
throw new ArgumentNullException("service");
// Building the initial request.
var request = service.Files.List();
request.PageSize = 100;
//request.Fields = "nextPageToken, files(id,name,size,description,createdTime,webViewLink)";
// Applying optional parameters to the request.
request = (FilesResource.ListRequest)SampleHelpers.ApplyOptionalParms(request, optional);
var pageStreamer = new Google.Apis.Requests.PageStreamer<Google.Apis.Drive.v3.Data.File, FilesResource.ListRequest, Google.Apis.Drive.v3.Data.FileList, string>(
(req, token) => request.PageToken = token,
response => response.NextPageToken,
response => response.Files);
var allFiles = new FileList();
allFiles.Files = new List<File>();
foreach (var result in pageStreamer.Fetch(request))
{
allFiles.Files.Add(result);
}
return allFiles;
}
catch (Exception Ex)
{
throw new Exception("Request Files.List failed.", Ex);
}
}
When I try to ask more information with this request :
request.Fields = "nextPageToken, files(id,name,size,description,createdTime,webViewLink)";
I have only 29 results (allFiles) with all the needed information but in fact I have 1699 file.
When I don't specify any fields request I can list all the file but I only have ID and Name File.
Related
Currently I'm implementing Microsoft Authentication Library(MSAL) on my C# .NET framework webapp (single tenant) and when I acquire the token using the code from Owin I'm getting the wrong GUID for the user/tenant in my confidential app.
This is the return from the confidential app(dc3... is the UserId and cf3.. is the TenantId), this is from a different directory on Azure.
But the claims generated by C# have the correct values:
If I check the object from the confidential app I can see inside "TenantProfiles" the same values as the above (f81 and e24), the correct ones.
But since the Claims have different values as the Confidential App, I cannot get the user with GetAccountAsync(), because it tries to find a user based on "dc3" GUID not "f81" GUID. I can get the user using a filter on GetAccountsAsync(), but this method is deprecated.
Here's my code
public static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; //https://login.microsoftonline.com/{0}
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant) + "/v2.0";
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() { CookieSecure = CookieSecureOption.SameAsRequest });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = Startup.Authority,
ClientId = Startup.clientId,
RedirectUri = Startup.redirectUri,
PostLogoutRedirectUri = Startup.redirectUri,
Scope = OpenIdConnectScopes.OpenIdProfile,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthorizationFailed
}
});
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
var app = IdentityApiUtility.BuildConfidentialClientApplication();
var result = await app.AcquireTokenByAuthorizationCode(new[] { "https://graph.microsoft.com/.default" }, notification.Code).ExecuteAsync();
}
and
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
if (clientapp == null)
{
clientapp = ConfidentialClientApplicationBuilder
.Create(Startup.clientId)
.WithClientSecret(Startup.appKey)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(Startup.Authority)
.Build();
}
return clientapp;
}
/// <summary>
/// Gets an auth code on behalf of the current user
/// </summary>
private AuthenticationResult GetOpenIdConnectAuth()
{
try
{
string userObjectID = $"{ClaimsPrincipal.Current.GetObjectId()}.{ClaimsPrincipal.Current.GetTenantId()}";
var app = BuildConfidentialClientApplication();
var scopes = new[] { "https://graph.microsoft.com/.default" };
//The userObjectId here starts with f81, which I got from the claims. But the user in the ConfidentialApp starts with dc3 which from another Azure Directory
var account = app.GetAccountAsync(userObjectID).Result;
var accessToken = app.AcquireTokenSilent(scopes, account).ExecuteAsync().Result;
return accessToken;
}
catch (Exception ex)
{
throw new Exception("Authentication Error in GetOpenIdConnectAuth method");
}
}
I already checked clientid/secret/tenant multiple times just to be sure that I wasn't sending the wrong authority/tenant and this is not the case. Does anyone have a suggestion how I can get the user from the ConfidentialApp or what I'm doing wrong?
I'm trying to create an online meeting from ASP.NET Core Web API, but I'm getting this "generalException" error.
AuthenticationProvider for configuring auth for the API call:
Code:
public class GraphAuthenticationProvider : IAuthenticationProvider
{
public const string GRAPH_URI = "https://graph.microsoft.com/v1.0/";
/// <summary>
/// Default constructor
/// </summary>
/// <param name="configuration"></param>
public GraphAuthenticationProvider(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{Configuration["AzureAd:TenantId"]}");
ClientCredential credentials = new ClientCredential(Configuration["AzureAd:ClientId"], Configuration["AzureAd:ClientSecret"]);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(GRAPH_URI, credentials);
request.Headers.Add("Authorization", "Bearer " + authResult.AccessToken);
request.Headers.Add("Content-type", "application/json");
}
}
GraphProvider for making the actual API request:
public class MicrosoftGraphProvider : IGraphProvider
{
private IGraphServiceClient _graph;
public MicrosoftGraphProvider(IGraphServiceClient graph)
{
_graph = graph;
}
public async Task<CreateMeetingResult> CreateMeetingAsync(OnlineMeeting onlineMeeting)
{
// Initialize error message
var errorMessage = default(string);
// Initialize meeting result
var meetingResult = default(OnlineMeeting);
try
{
// Try creating the meeting
meetingResult = await _graph.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
}
catch (Exception ex)
{
// Set the error message
errorMessage = ex.Message;
}
// Return the result
return new CreateMeetingResult
{
ErrorPhrase = errorMessage,
MeetingResult = meetingResult
};
}
}
AuthenticationProvider, GraphServiceClient, and GraphProvider transient instances in StartUp.cs:
services.AddTransient<IAuthenticationProvider, GraphAuthenticationProvider>(provider =>
new GraphAuthenticationProvider(provider.GetService<IConfiguration>()));
// Add transient instance of the graph service client
services.AddTransient<IGraphServiceClient, GraphServiceClient>(provider =>
new GraphServiceClient(provider.GetService<IAuthenticationProvider>()));
// Add transient instance of the graph provider
services.AddTransient<IGraphProvider, MicrosoftGraphProvider>(provider =>
new MicrosoftGraphProvider(provider.GetService<IGraphServiceClient>()));
Setting OnlineMeeting data and invoking CreatingMeeting:
var onlineMeeting = new OnlineMeeting
{
Subject = meetingDetails.Subject,
AllowedPresenters = OnlineMeetingPresenters.Organizer,
IsEntryExitAnnounced = true,
Participants = new MeetingParticipants
{
Attendees = new List<MeetingParticipantInfo>
{
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Attendee,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee1"
}
}
},
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee2"
}
}
},
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee3"
}
}
}
},
Organizer = new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = Framework.Construction.Configuration["OnlineMeeting:Organiser:DisplayName"]
}
}
}
},
EndDateTime = DateTimeOffset.Now.AddHours(1),
StartDateTime = DateTimeOffset.Now.AddHours(2)
};
// Fire create meeting
var meetingResult = await _graphProvider.CreateMeetingAsync(onlineMeeting);
ApiResponse:
{
"response": null,
"successful": false,
"errorMessage": "Code: generalException\r\nMessage: An error occurred sending the request.\r\n"
}
I have created an application in azure app service and added all necessary permissions as well.
What is it that I'm not doing correctly?
If you require more info, please ask me.
Thanks in advance.
I'm not sure where the error happened, but there are some problems in your code.
As I said in the comment, the resource should be https://graph.microsoft.com/. https://graph.microsoft.com/v1.0/ is the URL of Microsoft Graph API. Please see this article.
Here are some examples of Microsoft web-hosted resources:
Microsoft Graph: https://graph.microsoft.com
Microsoft 365 Mail API: https://outlook.office.com
Azure Key Vault: https://vault.azure.net
Try to replace Me with Users[<user-id>].
You use ClientCredential to authorize, which means you are using the Client credential flows. The OAuth 2.0 client credentials grant flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. So you could not call Microsoft Graph API with .Me which is used for the signed-in user.
I'm trying to follow the documentation for analytics reporting, but I'm getting pretty confused between the documentation for Oauth2 (which is in C# but I can't get to work), and the documentation for the analytics reporting API is very sparse (it has examples in Java and Python, but not C#). I've been reading over the following documentation:
https://developers.google.com/analytics/devguides/reporting/core/v4/
https://developers.google.com/analytics/devguides/reporting/core/v4/
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
Analytics Reporting API V4 Client Library for .NET
I've put together some code based on the Stack Overflow answer and the Google Developers documentation on Oauth2 for Asp.NET MVC. here is my code:
class GoogleAnalyticsUtil
{
public List<AnalyticsPage> GetReport(DateTime startDate)
{
List<AnalyticsPage> report = new List<AnalyticsPage>();
try
{
var credentialTask = GetCredential();
credentialTask.Wait();
var credential = credentialTask.Result;
using (var svc = new AnalyticsReportingService(
new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Google Analytics API Console"
}))
{
var dateRange = new DateRange
{
StartDate = startDate.ToString("yyyy-MM-dd"),
EndDate = DateTime.Now.ToString("yyyy-MM-dd")
};
var sessions = new Metric
{
Expression = "ga:sessions",
Alias = "Sessions"
};
var date = new Dimension { Name = "ga:date" };
var reportRequest = new ReportRequest
{
DateRanges = new List<DateRange> { dateRange },
Dimensions = new List<Dimension> { date },
Metrics = new List<Metric> { sessions },
ViewId = "<<view id>>"
};
var getReportsRequest = new GetReportsRequest
{
ReportRequests = new List<ReportRequest> { reportRequest }
};
var batchRequest = svc.Reports.BatchGet(getReportsRequest);
var response = batchRequest.Execute();
foreach (var x in response.Reports.First().Data.Rows)
{
report.Add(new AnalyticsPage()
{
Path = x.Dimensions.First(),
Views = x.Metrics.First().Values
});
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return report;
}
static async Task<UserCredential> GetCredential()
{
var clientSecretPath = HttpRuntime.AppDomainAppPath + "client_secret.json";
var credPath = HttpRuntime.AppDomainAppPath + "credentials/GoogleAnalyticsApiConsole/";
UserCredential credential;
using (var stream = new FileStream(clientSecretPath,
FileMode.Open, FileAccess.Read))
{
var secrets = GoogleClientSecrets.Load(stream).Secrets;
credential = await dsAuthorizationBroker.AuthorizeAsync(
secrets,
new[] {AnalyticsReportingService.Scope.Analytics},
"<<username>>",
CancellationToken.None,
new FileDataStore(credPath, true));
return credential;
}
}
}
class AnalyticsPage
{
public string Path { get; set; }
public IList<string> Views { get; set; }
public string ViewString { get; set; }
}
public class dsAuthorizationBroker : GoogleWebAuthorizationBroker
{
public static string RedirectUri = "https://<<my website>>/AuthCallback/IndexAsync";
public new static async Task<UserCredential> AuthorizeAsync(
ClientSecrets clientSecrets,
IEnumerable<string> scopes,
string user,
CancellationToken taskCancellationToken,
IDataStore dataStore = null)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = clientSecrets,
};
return await AuthorizeAsyncCore(initializer, scopes, user,
taskCancellationToken, dataStore).ConfigureAwait(false);
}
private static async Task<UserCredential> AuthorizeAsyncCore(
GoogleAuthorizationCodeFlow.Initializer initializer,
IEnumerable<string> scopes,
string user,
CancellationToken taskCancellationToken,
IDataStore dataStore)
{
initializer.Scopes = scopes;
initializer.DataStore = dataStore ?? new FileDataStore(Folder);
var flow = new dsAuthorizationCodeFlow(initializer);
return await new AuthorizationCodeInstalledApp(flow,
new LocalServerCodeReceiver())
.AuthorizeAsync(user, taskCancellationToken).ConfigureAwait(false);
}
}
public class dsAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public dsAuthorizationCodeFlow(Initializer initializer)
: base(initializer) { }
public override AuthorizationCodeRequestUrl
CreateAuthorizationCodeRequest(string redirectUri)
{
return base.CreateAuthorizationCodeRequest(dsAuthorizationBroker.RedirectUri);
}
}
namespace MySite.Web.Controllers.WebAPI
{
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
[System.Web.Mvc.HttpGet]
public async Task IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "PMA"
});
// YOUR CODE SHOULD BE HERE..
// SAMPLE CODE:
//var list = await service.Files.List().ExecuteAsync();
}
Response.Redirect(result.RedirectUri);
}
}
}
The authorization in GetCredential() is failing, with the error
`Failed to launch browser with "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&client_id=224691372209-3ljoils93ufa13mgk2ahilvniqa30f2p.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fmysite.com%2FAuthCallback%2FIndexAsync&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics" for authorization. See inner exception for details.
When I try to go directly to mysite.com/AuthCallback/IndexAsync, I get a 404, so maybe that's the problem? But I'm not sure what's wrong with the controller, or why that's causing the authorization to fail. I'm using a Web Application Auth2.0 client ID, but I also tried using type 'Other' which doesn't take RedirectUrls in the parameters, but that didn't work either. Can I get it to authorize without redirecting anywhere? I need credential.result from the authorization to continue with the rest of my code, or is there another way in C# that I should be getting data from the Google Analytics Reporting API?
I'm working on my own ChatBot for YouTube in Winforms and C#. It already works on Twitch and I'm trying to replicate the functionality for Youtube using the C# API. I can download the Chat Messages no problem, but creating a chat message is causing me a headache as I'm getting a 403, Insufficient Permission error. The full error message is
Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]
]
After some searching I've tried most things that I can find and have still come up empty as to what exactly is causing this. I get it's a permission issue and I obviously need to set something but I can't figure out what. My code is below and definitely works for reading data... but i don't know why it won't work for writing.
public class YouTubeDataWrapper
{
private YouTubeService youTubeService;
private string liveChatId;
private bool updatingChat;
private int prevResultCount;
public List<YouTubeMessage> Messages { get; private set; }
public bool Connected { get; private set; }
public bool ChatUpdated { get; set; }
//public Authorisation Authorisation { get; set; }
//public AccessToken AccessToken { get; set; }
public YouTubeDataWrapper()
{
this.Messages = new List<YouTubeMessage>();
}
public async void Connect()
{
Stream stream = new FileStream("client_secrets.json", FileMode.Open);
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets, new[] { YouTubeService.Scope.YoutubeForceSsl }, "user", CancellationToken.None, new FileDataStore(this.GetType().ToString()));
stream.Close();
stream.Dispose();
this.youTubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var res = this.youTubeService.LiveBroadcasts.List("id,snippet,contentDetails,status");
res.BroadcastType = LiveBroadcastsResource.ListRequest.BroadcastTypeEnum.Persistent;
res.Mine = true;
//res.BroadcastStatus = LiveBroadcastsResource.ListRequest.BroadcastStatusEnum.Active;
var resListResponse = await res.ExecuteAsync();
IEnumerator<LiveBroadcast> ie = resListResponse.Items.GetEnumerator();
while (ie.MoveNext() && string.IsNullOrEmpty(this.liveChatId))
{
LiveBroadcast livebroadcast = ie.Current;
string id = livebroadcast.Snippet.LiveChatId;
if (!string.IsNullOrEmpty(id))
{
this.liveChatId = id;
this.Connected = true;
}
bool? def = livebroadcast.Snippet.IsDefaultBroadcast;
string title = livebroadcast.Snippet.Title;
LiveBroadcastStatus status = livebroadcast.Status;
}
}
public async void UpdateChat()
{
if (!this.updatingChat)
{
if (!string.IsNullOrEmpty(this.liveChatId) && this.Connected)
{
this.updatingChat = true;
var livechat = this.youTubeService.LiveChatMessages.List(this.liveChatId, "id,snippet,authorDetails");
var livechatResponse = await livechat.ExecuteAsync();
PageInfo pageInfo = livechatResponse.PageInfo;
this.ChatUpdated = false;
if (pageInfo.TotalResults.HasValue)
{
if (!this.prevResultCount.Equals(pageInfo.TotalResults.Value))
{
this.prevResultCount = pageInfo.TotalResults.Value;
this.ChatUpdated = true;
}
}
if (this.ChatUpdated)
{
this.Messages = new List<YouTubeMessage>();
foreach (var livemessage in livechatResponse.Items)
{
string id = livemessage.Id;
string displayName = livemessage.AuthorDetails.DisplayName;
string message = livemessage.Snippet.DisplayMessage;
YouTubeMessage msg = new YouTubeMessage(id, displayName, message);
string line = string.Format("{0}: {1}", displayName, message);
if (!this.Messages.Contains(msg))
{
this.Messages.Add(msg);
}
}
}
this.updatingChat = false;
}
}
}
public async void SendMessage(string message)
{
LiveChatMessage liveMessage = new LiveChatMessage();
liveMessage.Snippet = new LiveChatMessageSnippet() { LiveChatId = this.liveChatId, Type = "textMessageEvent", TextMessageDetails = new LiveChatTextMessageDetails() { MessageText = message } };
var insert = this.youTubeService.LiveChatMessages.Insert(liveMessage, "snippet");
var response = await insert.ExecuteAsync();
if (response != null)
{
}
}
}
The main code in question is the Send Message method. I've tried changing the Scope of the UserCredentials to everything I can try to no avail. Any ideas?
From the YouTube Data API - Error, your error 403 or insufficientPermissions is error in the OAuth 2.0 token provided for the request specifies scopes that are insufficient for accessing the requested data.
So make sure you use proper Scope in your application. Here is the example of scope that is needed in your application.
https://www.googleapis.com/auth/youtube.force-ssl
https://www.googleapis.com/auth/youtube
For more information about this error 403, you can check this related SO question.
Turns out that revoking the access and then re-doing it fixed the issue. The error message was not very helpful.
Ok, so this one has been highly frustrating and I cannot seem to figure out how to fix it, hoping someone can point me in the right direction.
So if I may give a run down of the architecture:
Solution
IdentityServer Layer
WebApi Layer
MVC layer (as the client)
The Issue
When trying to access the user that has been authenticated by the IdentityServer it always returns false
if (this.User.Identity.IsAuthenticated)
{
var identity = this.User.Identity as ClaimsIdentity;
foreach (var claim in identity.Claims)
{
Debug.Write(claim.Type + " " + claim.Value);
}
}
I've spent many hours trying to figure why I do not have access to the user.
IdentityServer
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get());
var options = new IdentityServerOptions
{
Factory = idServerServiceFactory,
SiteName = "Proderty Security Token Service",
IssuerUri = "https://localhost:44300/identity",
PublicOrigin = "https://localhost:44300/",
SigningCertificate = LoadCertificate()
};
idsrvApp.UseIdentityServer(options);
});
}
public static IEnumerable<Client> Get()
{
return new[]
{
new Client()
{
ClientId = "prodertyclientcredentials",
ClientName = "Proderty (Client Credentials)",
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("Some Secret here".Sha256())
},
AllowAccessToAllScopes = true
},
new Client()
{
ClientId = "prodertyauthcode",
ClientName = "Proderty (Authorization Code)",
Flow = Flows.AuthorizationCode,
RedirectUris = new List<string>()
{
"http://localhost:10765/prodertycallback"
},
ClientSecrets = new List<Secret>()
{
new Secret("Some Secret here".Sha256())
},
AllowAccessToAllScopes = true
},
new Client()
{
ClientId = "prodertyhybrid",
ClientName = "Proderty (Hybrid)",
Flow = Flows.Hybrid,
RedirectUris = new List<string>()
{
"http://localhost:10765"
},
AllowAccessToAllScopes = true
}
};
}
public static IEnumerable<Scope> Get()
{
return new List<Scope>()
{
new Scope
{
Name = "prodertymanagment",
DisplayName = "Proderty Management",
Description = "Allow the application to manage proderty on your behalf.",
Type = ScopeType.Resource
},
StandardScopes.OpenId,
StandardScopes.Profile
};
}
return new List<InMemoryUser>()
{
new InMemoryUser()
{
Username = "Terry",
Password = "Bob",
Subject = "b053d546-6ca8-b95c-77e94d705ddf",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Terry"),
new Claim(Constants.ClaimTypes.FamilyName, "Wingfield")
}
},
new InMemoryUser()
{
Username = "John",
Password = "Bob",
Subject = "bb61e881-3a49-8b62-c13dbe102018",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "John"),
new Claim(Constants.ClaimTypes.FamilyName, "Borris")
}
}
};
As you can see the IdentityServer seems positively intact. Once called I expect that I should be access the user on the MVC client.
WebApi
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
RequiredScopes = new[] { "prodertymanagment" }
});
}
[Authorize]
public class TestController : ApiController
{
// GET api/<controller>
public string GetOne()
{
return "If you can see this, then we must be authorized! - GETONE - Coming From Test Controller";
}}
}
MVCClient
public static HttpClient GetClient()
{
var client = new HttpClient();
var accessToken = Token.RequestAccessTokenAuthorizationCode();
if (accessToken != null)
{
client.SetBearerToken(accessToken);
}
client.BaseAddress = new Uri("https://localhost:44301/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public static string RequestAccessTokenAuthorizationCode()
{
var cookie = HttpContext.Current.Request.Cookies.Get("ProdertyCookie");
if (cookie != null && cookie["access_token"] != null)
{
return cookie["access_token"];
}
var authorizeRequest = new IdentityModel.Client.AuthorizeRequest(
"https://localhost:44300/identity/connect/authorize"
);
var state = HttpContext.Current.Request.Url.OriginalString;
var url = authorizeRequest.CreateAuthorizeUrl(
"prodertyauthcode",
"code",
"prodertymanagment",
"http://localhost:10765/prodertycallback",
state);
HttpContext.Current.Response.Redirect(url);
return null;
}
// GET: ProdertyCallback
public async Task<ActionResult> Index()
{
var authCode = Request.QueryString["code"];
var client = new TokenClient(
"https://localhost:44300/identity/connect/token",
"prodertyauthcode",
"Some Secret here"
);
var tokenResponse = await client.RequestAuthorizationCodeAsync(
authCode,
"http://localhost:10765/prodertycallback"
);
Response.Cookies["ProdertyCookie"]["access_token"] = tokenResponse.AccessToken;
return Redirect(Request.QueryString["state"]);
}
With the code above everything works, If not logged in I'm pushed to the IdentityServer login page, My callback fires and then returned to the originating page. I also have a access token.
Now that I'm authorized to access the page, I expect to have access to the user but I don't which is highly frustrating.
I'm very aware that it's the app that's being authenticated but I'm sure the purpose was to access the user that authenticated the app (As a user of the client system too).
public async Task<ActionResult> Index()
{
if (this.User.Identity.IsAuthenticated)
{
var identity = this.User.Identity as ClaimsIdentity;
foreach (var claim in identity.Claims)
{
Debug.Write(claim.Type + " " + claim.Value);
}
}
var httpClient = ProdertyHttpClient.GetClient();
var test = await httpClient.GetAsync("api/test/getone").ConfigureAwait(false);
if (test.IsSuccessStatusCode)
{
var stringContent = await test.Content.ReadAsStringAsync().ConfigureAwait(false);
return Content(stringContent);
}
else
{
return Content(ExceptionHelper.GetExceptionFromResponse(test).ToString());
}
}
The only thing that springs to mind is using the Authorize Attribute which attempts to use the core asp.net identity model which I have not install because I'll have a custom data store.
Any help greatly appreciated!