Access denied when using OAuth client credentials flow to create shared mailbox - exchange-basicauth

Short version:
How to correctly set up application permissions and/or role assignments and/or something else that I'm missing, so that application id/secret (OAuth client credentials) can be used to create shared mailboxes?
So far I've tried couple combinations of permissions/roles, e.g. Exchange.ManageAsApp with User Administrator (fe930be7-5e62-47db-91af-98c3a49a38b1), Exchange administrator (29232cdf-9323-42fd-ade2-1d097af3e4de) and bunch of other.
Details:
I have a bunch of powershell scripts used to automate various tasks on Exchange Online. So far I've been using basic auth, which I was able to successfully convert into OAuth password flow.
But to get rid of dependency on service account completely, I'd prefer to use credentials flow. In background I'm trying to do something like this:
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{TenantId}", false, _tokenCache);
var clientCredential = new ClientCredential(ClientId, ClientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(Resource, clientCredential);
var username = "OAuthUser#" + TenantId;
var password = authenticationResult.CreateAuthorizationHeader();
var executor = new ExolExecutor(username, password);
await executor.Execute(Script, cancellationToken);
where executor does the regular thing:
Create PSSession to https://outlook.office365.com/powershell-liveid?BasicAuthToOAuthConversion=true
Executes powershell script using
using PowerShell powershell = PowerShell.Create();
powershell.Runspace = runspace;
powershell.AddScript(script);
...
await Task.Factory.FromAsync(powershell.BeginInvoke(input, output), powershell.EndInvoke);
Remove PSSession
So far so good. Works perfectly fine with Get-Mailbox -ResultSize 1.
But when trying to create new shared mailbox New-Mailbox -Name "pko222" -DisplayName "pko222" -Alias "pko222" -Shared, I'm getting
CategoryInfo.Activity: New-Mailbox
CategoryInfo.Category: 1001
CategoryInfo.Reason: ADOperationException
CategoryInfo.TargetName:
CategoryInfo.TargetType:
ErrorDetails.Message:
ErrorDetails.RecommendedAction:
Exception.Message: Active Directory operation failed on DB7PR01A03DC005.EURPR01A003.prod.outlook.com. This error is not retriable. Additional information: Access is denied.
Active directory response: 00000005: SecErr: DSID-03152612, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0
FullyQualifiedErrorId: [Server=BEXP281MB0087,RequestId=88419a8e-78a4-4967-9bca-71d40feb5150,TimeStamp=10/6/2020 11:57:38 AM] [FailureCategory=Cmdlet-ADOperationException] 2C0312E5,Microsoft.Exchange.Management.RecipientTasks.NewMailbox
JWT token looks something like this:
{
"aud": "https://outlook.office365.com",
"iss": "https://sts.windows.net/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/",
"iat": 1601985127,
"nbf": 1601985127,
"exp": 1601989027,
"aio": "E2RgYFCOsw1iZj34elV49CH5zyd5AQ==",
"app_displayname": "XXXXXXXXXXX",
"appid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"appidacr": "1",
"idp": "https://sts.windows.net/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/",
"oid": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
"rh": "0.AAAAv9y4fwZQ_0G6_d1kLKJ_sarAXb_REQFHhc2EM1FNn9tIAAA.",
"roles": ["User.Read.All", "full_access_as_app", "Mail.ReadWrite", "MailboxSettings.ReadWrite", "User.ReadBasic.All", "Mailbox.Migration", "Mail.Read", "Mail.Send", "MailboxSettings.Read", "Exchange.ManageAsApp"],
"sid": "qqqqqqqq-qqqq-qqqq-qqqq-qqqqqqqqqqqq",
"sub": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
"tid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"uti": "CRytfXbD80y3ATmQvd-VAQ",
"ver": "1.0",
"wids": ["29232cdf-9323-42fd-ade2-1d097af3e4de", "88d8e3e3-8f55-4a1e-953a-9b9898b8876b", "fe930be7-5e62-47db-91af-98c3a49a38b1", "9360feb5-f418-4baa-8175-e2a00bac4301", "62e90394-69f5-4237-9190-012177145e10", "0997a1d0-0d1d-4acb-b408-d5ca73121e90"]
}

Fyi i managed to make it work on my side.
you just need to add the following param in the connection uri
&email=SystemMailbox{bb558c35-97f1-4cb9-8ff7-d53741dc928c}#yourtenantname.onmicrosoft.com
so the connection uri looks like :
https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true&email=SystemMailbox{bb558c35-97f1-4cb9-8ff7-d53741dc928c}#yourtenantname.onmicrosoft.com
just change the suffix from 'yourtenantname' with ... your tenant name! dont put the tenant guid !
https://learn.microsoft.com/en-us/answers/questions/451006/pssession-and-modern-auth.html

Related

MsalClientException IDW10104 from GetAccessTokenForAppAsync

I have an ASP.NET Core Web API set up as App Service in Azure with an App Registration in our AzureAd
In appsettings.json I have (anonimized)
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "ourdomain.co.uk",
"TenantId": "n9n999n9-9999-nnnn-9n9n9-9n9n9n9n9n9",
"ClientId": "81933a15-157f-45b0-bc32-3d7d6d62f4a7",
"Audience": "https://ourdomain.co.uk/breathe.notifications-service",
"ClientSecret": "a6a6a6a~EEizqWNa8itAAAjcrycxnCtxaVgKTFx"
},
That app has an API permission in Azure Ad that allows me to call another app service, Audit. The audit service does not have any specific scopes defined but it does have an app role called Audit.Write
In the calling API i need to get a token to call audit so I run this code
var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Note the call to GetAccessTokenForAppAsync rather than the more common GetAccessTokenForUserAsync
The scope string that I am passing is
https://ourdomain.co.uk/us.audit-service/.default
When I call GetAccessTokenForAppAsync it is failing with MSALException
IDW10104: Both client secret and client certificate cannot be null or
whitespace, and only ONE must be included in the configuration of the
web app when calling a web API. For instance, in the appsettings.json
file.
The client secret is in the AzureAd config, I am not specifying a certificate.
I now have this working and have two options but before I outline those I need to offer some extra background.
This Web Api and others we have created offer functionality to Azure Ad users and Azure B2C users. This functionality was first possible with Microsoft.Identity.Web 1.11.0 and we hjave been using 1.11.0 since it was released. However we always had an issue where we would generate thousands of exceptions because MSAL was getting confused ny which scheme to use.
We came across this blog post, Removing misleading IDX10501 logs when using multiple authentication schemes in ASP.NET Core 3.1 there is more detail in this github thread, https://github.com/oliviervaillancourt/blog/issues/3.
Our Startup.cs Configure Services looks like this
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(this.configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(this.configuration, "AzureAdB2C", "B2CScheme", true);
services.AddAuthentication("AzureAD_OR_AzureAdB2C")
.AddMicrosoftIdentityWebApi(
jwtBearerOptions =>
{
var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
jwtBearerOptions.ForwardDefaultSelector = context =>
{
var token = string.Empty;
if (context.Request.Headers.TryGetValue("Authorization", out var value))
{
string authorization = value;
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
}
if (token == null)
{
this.logger.LogInformation($"Cannot get the Token out of the Authorization header");
}
var jwtHandler = new JwtSecurityTokenHandler();
if (jwtHandler.CanReadToken(token))
{
var jwtToken = jwtHandler.ReadJwtToken(token);
var expectedB2CIssuer = $"{azureAdB2CConfig.GetValue<string>("Instance")}/{azureAdB2CConfig.GetValue<string>("TenantId")}/v2.0/";
if (string.Compare(jwtToken.Issuer, expectedB2CIssuer, true) == 0)
{
// Claim is from B2C so this request should be validated against the B2C scheme.
this.logger.LogInformation($"Request is with a B2C issued token so refer to B2CScheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
return "B2CScheme";
}
else
{
this.logger.LogInformation($"Request is not with a B2C issued token so refer to Bearer scheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
}
}
else
{
this.logger.LogInformation("Request token could not be read so refer to Bearer scheme");
}
return "Bearer";
};
},
identityOptions =>
{
var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
identityOptions.Instance = azureAdB2CConfig.GetValue<string>("Instance");
identityOptions.TenantId = "AzureAD_OR_AzureAdB2C";
identityOptions.ClientId = "AzureAD_OR_AzureAdB2C";
},
"AzureAD_OR_AzureAdB2C",
false);
services.AddControllers()
.AddNewtonsoftJson();
services.AddLogging(options =>
{
// hook the Console Log Provider
options.AddConsole();
options.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
// hook the Application Insights Provider
options.AddFilter<ApplicationInsightsLoggerProvider>(string.Empty, Microsoft.Extensions.Logging.LogLevel.Trace);
// pass the InstrumentationKey provided under the appsettings
options.AddApplicationInsights(this.configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
});
}
The logic used by the ForwardDefaultSelector is what helps us work with multiple schemes and forward ASP.NET to the right scheme.
Now back to the answer.
If I remove the ForwardDefaultSelector I no longer get the IDW10104 however that is what we use to remopve all the extraneous exceptions schemes so that is not really going to be workable.
The only viable option is to move the Web Api from the latest version of Microsoft.Identity.Web 1.21.1 to 1.16.0. The issue that is causing us to get the exception was introduced in 1.16.1. I will raise an issue on the MSAL github for 1.16.1. We were previously using 1.11.0.

Getting error using Google cloud client libraries for Go: unknown credential type: "impersonated_service_account"?

I am working with Google Cloud in Go and following this article by John Hanley:
https://www.jhanley.com/google-cloud-improving-security-with-impersonation/
and mashed it with this SO answer:
How to authenticate Google APIs (Google Drive API) from Google Compute Engine and locally without downloading Service Account credentials?
The credentials are successfully saved to, "application_default_credentials.json":
Notice: "type": "impersonated_service_account"
{
"delegates": [],
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[sa#example-2021.iam.gserviceaccount.com]:generateAccessToken",
"source_credentials": {
"client_id": "...apps.googleusercontent.com",
"client_secret": "...",
"refresh_token": "...",
"type": "authorized_user"
},
"type": "impersonated_service_account"
}
My code which produces an unknown credential type: "impersonated_service_account" error:
package main
import (
...
"cloud.google.com/go/storage"
"golang.org/x/oauth2"
"google.golang.org/api/docs/v1"
"google.golang.org/api/drive/v3"
"google.golang.org/api/impersonate"
"google.golang.org/api/option"
...
)
var Config.GoogleServiceAccount string = "sa#example-2021.iam.gserviceaccount.com"
func main(){
_ = getTokenAsImpersonator()
}
// From: https://pkg.go.dev/google.golang.org/api/impersonate#example-CredentialsTokenSource-ServiceAccount
func getTokenAsImpersonator() oauth2.TokenSource {
ctx := context.Background()
// Base credentials sourced from ADC or provided client options.
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: Config.GoogleServiceAccount,
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
// Delegates: []string{"bar#project-id.iam.gserviceaccount.com"},
})
if err != nil {
log.Fatal(err)
}
return ts
}
The 'unknown credential type: "impersonated_service_account"' error:
google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: unknown credential type: "impersonated_service_account"
Have I done something wrong or is this a bug?
UPDATE
Answering John's questions from the comments:
1.
a) What is the value of the environment variable GOOGLE_APPLICATION_CREDENTIALS?
GOOGLE_APPLICATION_CREDENTIALS=/Users/x/.config/gcloud/application_default_credentials.json
b) What command did you use to generate application_default_credentials.json?
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/accounts.reauth,openid --impersonate-service-account=[sa#example-2021.iam.gserviceaccount.com]
Response:
Credentials saved to file: [/Users/x/.config/gcloud/application_default_credentials.json]
c)Which OS and version?
MacOS 10.13.6
d)gcloud --version?
Google Cloud SDK 343.0.0
app-engine-go
app-engine-python 1.9.91
bq 2.0.69
cloud-datastore-emulator 2.1.0
core 2021.05.27
gsutil 4.62
If you can create a minimum example ...
I have updated the example code above.
At some point I had used the CLI to impersonate an account:
gcloud config set auth/impersonate_service_account <service account>
Then later on when trying to use the application default credentials command it wraps your credentials with the service account credentials.
gcloud auth application-default login
What you end up with is a file that looks like this:
{
"delegates": [],
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/example#example-project.iam.gserviceaccount.com:generateAccessToken",
"source_credentials": {
"client_id": "123abc.apps.googleusercontent.com",
"client_secret": "XXXXXXXXX",
"refresh_token": "XXXXXXXXX",
"type": "authorized_user"
},
"type": "impersonated_service_account"
}
This appears to cause a lot of problems with third party services such as terraform.
What is strange is that Terraform is just making API calls to Google using Google SDKs, so really its something to do with Google.
You need to remove the impersonation:
gcloud config unset auth/impersonate_service_account
And then run the application default credential command again:
gcloud auth application-default login
Now if you check your file it should look like this:
{
"client_id": "XXXXXXXXX",
"client_secret": "XXXXXXXXX",
"quota_project_id": "example-project",
"refresh_token": "XXXXXXXXXX",
"type": "authorized_user"
}
I was hitting the same issue when I was trying to impersonate an account so I could run Terraform commands as a service account instead of my personal account but it doesn't like that.
EDIT: Rereading you question it sounds like you're in the same boat as me. We want to use service accounts without physically downloading the keys. This is even mentioned by Google as best practice. But doing so is causing issues with their own SDKs.
I had the same issue running GCP Terraform provider tests. You can specify the Service Account Terraform have to impersonate setting the env variable GOOGLE_IMPERSONATE_SERVICE_ACCOUNT (documentation).
Configuration steps:
export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=SERVICE_ACCOUNT#PROJECT_ID.iam.gserviceaccount.com
gcloud auth application-default login

How to exchange JWT token for Credentials in Cognito Identity Pool in .NET Core 3.1 WebApi

Broad Overview: I am trying to create a .Net Core 3.1 WebApi backend that is authenticated against Amazon Cognito. I want to use the Amazon-hosted sign-in page(s) provided by Cognito. I want to leverage Cognito Identity Pool to provide temporary scoped credentials for users after they have logged in. I cannot figure out how to exchange the Cognito token to create the Credentials to call AWS services.
Technology Overview
.NET Core 3.1 WebApi
Amazon Cognito User Pool for initial authentication
Amazon Identity Pool for defining permissions (Roles) for logged in users
Deployed on AWS via API Gateway + Lambda using the AWS Serverless framework (basically CloudFormation)
Currently both of the following work:
Add [Authorize] attribute to a controller endpoint and access the URL in a browser. This re-directs me to the Cognito-hosted login page and, upon successful login, returns me back to the controller/endpoint and I am authorized.
Create a separate Client application and login to AWS Cognito. Pass the JWT token in the Authorization HTTP header when calling APIs from the client and the Authorization succeeds and API access is granted.
In both cases, the access to the API is permitted however the AmazonServiceClient instances that are created in the WebApi are granted the permissions associated with the Lambda function (which is the proper behavior).
Problem
I need to create AmazonServiceClients whose credentials match the Role defined by the Cognito Identity Pool.
To do this, I need to exchange token provided by logging into Cognito User Pool for temporary credentials in the Identity Pool.
Virtually ALL examples and documentation I can find on this process define how to manually login to Cognito using the API (not the hosted web UI), and then using the API response to create a CognitoUser and then get credentials from the Identity Pool using that user.
The closest (though super brief) documentation I can find to do what I need is from AWS here: https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/cognito-creds-provider.html
// Authenticate user through Facebook
string facebookToken = GetFacebookAuthToken();
// Add Facebook login to credentials. This clears the current AWS credentials
// and retrieves new AWS credentials using the authenticated role.
credentials.AddLogin("graph.facebook.com", facebookAccessToken);
While that example uses Facebook, conceptually it should be the same for any provider (Facebook, Google, Twitter, OpenId, etc.).
My Current Attempt
I have registered CognitoAWSCredentials as a Scoped service as it is user-specific and therefore should only exist as long as the API request session exists.
RegionEndpoint region = Configuration.GetAWSOptions().Region;
services.AddScoped(_ => new CognitoAWSCredentials(Settings.CognitoIdentityPoolId, region));
I have created an event handler that gets triggered when the OpenIdConnect event 'OnTokenValidated' is fired. This happens after I login to the Cognito hosted web UI and am redirected back to my API.
In this handler I can call:
CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
creds.AddLogin( ... ??? ...);
(note: since I'm setting all this up in the Startup.ConfigureServices(IServiceCollection services) method, I am building an IServiceProvider instance each time authentication succeeds... which may be inefficient but I haven't figured out another way to access a scoped service inside the ConfigureServices method)
All this preamble to say that I cannot find a set of values for the AddLogin call which allow this test call to succeed:
ImmutableCredentials immCreds = creds.GetCredentials();
Relevant Data Structures
In the event handler where I can call AddLogin, I have access to: Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext which in particular contains:
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage with:
access_token
id_token
refresh_token
System.IdentityModel.Tokens.Jwt.JwtSecurityToken with:
{
{
"alg": "RS256",
"kid": "**************************"
}. {
"at_hash": "**************************",
"sub": "**************************",
"email_verified": true,
"iss": "https://cognito-idp.ca-central-1.amazonaws.com/**************************",
"cognito:username": "**************************",
"nonce": "**************************",
"aud": "**************************",
"event_id": "**************************",
"token_use": "id",
"auth_time": 1595260191,
"exp": 1595263791,
"iat": 1595260191,
"email": "**************************"
}
}
I have tried using the iss value as the providerName in AddLogin, and either the access_token or id_token but neither work.
Does anyone know what I need to use for AddLogin in order for Cognito to create Identity Pool credentials for me based upon a JWT token from a Cognito User Pool login?
unless I missed it, I haven't seen documentation that states this, but even though all the Issuer fields on the various data structures include the 'https://', you need to strip it before using the Issuer as the providerName on the AddLogin call. ugh.
CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
string shortIssuer = tokenValidatedContext.SecurityToken.Issuer;
if (shortIssuer.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("https://".Length);
if (shortIssuer.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("http://".Length);
creds.AddLogin(shortIssuer, tokenValidatedContext.TokenEndpointResponse.IdToken);
now, the above code has a problem as the services.BuildServiceProvider(). part means the credentials object I modify isn't global (only local to the service provider I built here I think), but that's a different issue - just noting that in case anyone is copying this code.
services...<other authentication setup>...
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ClientId = Settings.CognitoClientId;
options.MetadataAddress = CognitoMetadataAddress;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.UsePkce = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuers = new string[] { Settings.CognitoAuthority },
ValidateAudience = true,
ValidAudiences = new string[] { Settings.CognitoClientId }
};
options.Events = new OpenIdConnectEvents() {
OnTokenValidated = tokenValidatedContext => {
CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
string shortIssuer = tokenValidatedContext.SecurityToken.Issuer;
if (shortIssuer.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("https://".Length);
if (shortIssuer.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("http://".Length);
creds.AddLogin(shortIssuer, tokenValidatedContext.TokenEndpointResponse.IdToken);
return Task.CompletedTask;
}
};
})
(some code removed to focus on specifically the OpenId Connect event and the CognitoAWSCredentials init)

Override default Identity Server 4 Client

I created a .NET Core 3.0 Angular project with Identity Server. I want to add claims and roles to my app.
My Identity Server is mostly out of the box with some simple route changes:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/auth/login";
options.UserInteraction.LogoutUrl = "/auth/logout";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
I currently add my a simple policy in startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy =>
{
policy.RequireRole("Admin");
policy.RequireClaim("CreateUser");
});
});
At the bottom of my Configure() method I call this method to add roles:
private async Task CreateUserRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult adminRoleResult;
//Adding Admin Role
var adminRoleCheck = await RoleManager.RoleExistsAsync("Admin");
if (!adminRoleCheck)
{
//create the roles and seed them to the database
adminRoleResult = await RoleManager.CreateAsync(new IdentityRole("Admin"));
await RoleManager.AddClaimAsync(new IdentityRole("Admin"), new Claim("CreateUser", "Create User"));
}
}
In my register.cshtml.cs I've successfully set a role and a claim via:
var roleResult = await _userManager.AddToRoleAsync(user, "Admin");
var claimResult = await _userManager.AddClaimAsync(user, new Claim("CreateUser", "Create User"));
I've confirmed that the new user has that claim and role.
The client userinfo call returns correctly but when I look at the id_token I dont have those claims:
{
"nbf": 1574787988,
"exp": 1574788288,
"iss": "https://localhost:5001",
"aud": "MyAppSpa",
"iat": 1574787988,
"at_hash": "ndzldxAE3EiVzI4PeThNPQ",
"s_hash": "dIqJXx372XhOESn1XYH36A",
"sid": "VQLp--MHdoOoxXiVASWZ0g",
"sub": "4a0450dd-fe4f-4b3d-ac12-3f70876183e1",
"auth_time": 1574787983,
"idp": "local",
"amr": [
"pwd"
]
}
According to oidc-client-js is not getting claims correctly from Identity Server 4, I should just need to include AlwaysIncludeUserClaimsInIdToken = true to the client configuration.
However, the template doesnt have a configuration. Its all under the hood.
Questions:
1) Will adding AlwaysIncludeUserClaimsInIdToken = true to the client fix my issue?
2) How do I add it given my current configuration?
Concern about the size of ID Token , by default the claims won't include in ID token .You can get the claims from userinfo endpoint with ID token(read from user.profile) . That is by design .
The new .net core 3.0 angular authentication template just configures IdentityServer to use template supported configuration , but the configuration is not fully customize compare to Identity server provided configuration ,such as AlwaysIncludeUserClaimsInIdToken . So the workaround is not use ApiAuthorization service, the full power of IdentityServer is still available to customize authentication to suit your needs.

YouTube API v.3 with GoogleCredential (OAuth2): My YouTube credentials get "Insufficient Permission" when createPlaylist or uploadVideo

The following code does fine when I search for videos using Google's YouTube API (version 3) but fails when attempting:
Playlist youTubePlaylist = new Playlist();
youTubePlaylist.setSnippet(playlistSnippet);
youTubePlaylist.setStatus(playlistStatus);
YouTube.Playlists.Insert command = youTube.playlists().insert("snippet,status", youTubePlaylist);
youTubePlaylist.setKey("AI...1IU");
youTubePlaylist.execute();
On the credential step I do get an accessToken but failure happens on youTubePlaylist.execute():
{
"access_token" : "ya29...DSoQ",
"expires_in" : 3600,
"token_type" : "Bearer"
}
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Insufficient Permission"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:312)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1049)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
at ...publication.youtube.YouTubeAPIVersion3Connector.execute(YouTubeAPIVersion3Connector.java:107)
at ...publication.youtube.YouTubeAPIVersion3ServiceImpl.createPlaylist(YouTubeAPIVersion3ServiceImpl.java:119)
The authentication is done as follows:
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
File privateKeyP12 = new File(getYouTubeConfigDirectory(), "/private-key-youtube-v3.p12");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("99...#developer.gserviceaccount.com")
.setServiceAccountScopes(Collections.list(YouTubeScopes.YOUTUBE_READONLY, YouTubeScopes.YOUTUBE_UPLOAD))
.setServiceAccountPrivateKeyFromP12File(privateKeyP12)
.setClientSecrets("9...42.apps.googleusercontent.com", "DH...w8")
.setRefreshListeners(getRefreshListeners())
.build();
}
return new YouTube.Builder(httpTransport, jsonFactory, credential).setApplicationName(googleAppAccountName).build();
Keep in mind, I am NOT performing YouTube operations on behalf of another user. My web application is managing videos on its own YouTube channel. What I've done already:
via Google Cloud Console, I enabled YouTube v3 API, created an app with permission to edit
I generated p12 private key file which is loaded by code above
I use API key on all operations
What am I missing? For example:
- what am I supposed to do with the Public Key Fingerprint provided by Google Cloud Console?
- am I properly setting setServiceAccountScopes() with only YouTubeScopes.YOUTUBE_READONLY and YouTubeScopes.YOUTUBE_UPLOAD?
- Is there some way I can control permissions beyond setting "Can Edit" on Cloud Console for email: 99...91i#developer.gserviceaccount.com?
Part of my frustration is that all code samples provided by YouTube v3 API site involve spinning up a "headless" AWT toolkit. All my app wants to do is authenticate using its own YouTube account.
Thank you for your suggestion.
Service accounts are not supported in Data API v3. Please create a project id in cloud console and use your client secret and id.
Although we are aware of the need, we don't have news about this availability at this point.
Here are our Java samples, that can guide you.

Resources