I have a C# project using MVC and OWIN.
I am trying to allow the use of multiple OpenID providers. I've seen how people do it by passing the parameter string in using "RedirectToIdentityProvider" in the pipeline like in this article: https://www.scottbrady91.com/Identity-Server/Identity-Server-3-Standalone-Implementation-Part-2
I am, however, not using any of the big name providers like google or github. I'm making it applicable to any Id Provider that supports OIDC.
The problem is I want to set the Authority dynamically in the RedirectToIdentityProvider Task...but I don't know how.
private async Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var openIdProviderService = new OpenIdProviderService();
var openIdProvider = openIdProviderService.Get("SecureAuthA");
notification.ProtocolMessage.ClientId = openIdProvider.ClientId;//"3b11fd9d1d6f454c8ed09b837ed632cb";
notification.ProtocolMessage.ClientSecret = openIdProvider.ClientSecret;//"6e687e0f551fe32ea7df63a553f73a2206b8c69e0da436efb5191c229528bef4";
Is there a way to set the Authority dynamically?
Related
I am developing an application which is based on GCP (Specifically it runs on the Google Cloud Composer (a managed version of Airflow)). From there I would like to connect to the Microsoft Graph API of another organization. So I want the application to be running in the background and every x minutes retrieve new emails from the inbox of an external organization, then do some processing and based on that perform some actions in said mailbox through the Graph API.
I am trying to figure out the best practice on how to secure this connection. I believe I could use the client secret of the registered application and then store that in an azure keyvault. Now I am not sure how I should authenticate my registered app such that it can retrieve this client secret which in turn can be used to access the Graph API?
As you can probably tell I'm not quite sure whether this makes sense or what a better approach would be? Almost all the information I am finding is concerning managed identities, however if I understood correctly these can only be used when the application is running natively on Azure, which for me is not the case.
Would really appreciate any help!
If I don't misunderstand, you can refer to my sample code, pls note you need to add key vault access policy first. And you may refer to this doc to know about the default azure credential.
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace test0430callingapi.Controllers
{
public class HelloController : Controller
{
public async Task<string> IndexAsync()
{
const string secretName = "clientsecret";
var kvUri = "https://key_vault_name.vault.azure.net/";
var a = new DefaultAzureCredential();
var client = new SecretClient(new Uri(kvUri), a);
var secret = await client.GetSecretAsync(secretName);
string secretVaule = secret.Value.Value;
//azure ad client credential flow to generate access token
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret(secretVaule)
.WithAuthority(new Uri("https://login.microsoftonline.com/your_tanent_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
string accesstoken = result.AccessToken;
// this token can be used to call https://graph.microsoft.com/v1.0/users/user_id/mailFolders/{id}/messages
return accesstoken;
}
}
}
I used client credential flow here which don't need to make users sign in, and this api support application permission. If you use an api which only supports delegated permission, it can't work. Here's the api calling response.
I'm running ASP.NET Core application (.Net Core 3.0) and have referenced nuGet package Microsoft.Azure.Management.WebSites. It seems like there are half a dozen ways to connect to Azure and I'm hoping that is the correct one for my environment.
I'm attempting to instantiate a WebSiteManagementClient so that I can modify some AppService settings (scale service plan up/down). To that end, I need an instance of ServiceClientCredentials. I can't seem to find a way to get the proper credentials together.
I've followed several different articles, all of them advocate a different method.
What's the easiest way to get authenticated against the Azure Management SDK?
Ideally, avoiding Azure Active Directory. I've attempted multiple times trying to set up an App Registration with the appropriate permissions, and I can't seem to get it together.
The app connecting and making the change will be an ASP.NET website running in Azure itself, if it makes a difference.
Thanks in advance!
Code so far:
using Microsoft.Azure.Management.WebSites;
var credentials = await GetCredentials(); // <-- ???
WebSiteManagementClient client = new WebSiteManagementClient(credentials);
client.SubscriptionId = "xxx-xxx-xxxx-xxxx";
Try this :
static void Main(string[] args)
{
string tenantId = "<your tenant ID>";
string clientId = "<your azure ad app ID>";
string clientSecret = "<azure ad app secret>";
string subscriptionId = "<your subscription ID>";
WebSiteManagementClient client = new WebSiteManagementClient(GetCredsFromServicePrincipal(tenantId, clientId, clientSecret));
client.SubscriptionId = subscriptionId;
foreach (var ap in client.app.List()) {
Console.WriteLine(ap.Id);
}
}
private static TokenCredentials GetCredsFromServicePrincipal(String tenantId,String clientId, String clientSecret)
{
var authority = #"https://login.microsoftonline.com/" + tenantId;
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, clientSecret);
var authResult = authContext.AcquireTokenAsync("https://management.azure.com", credential).GetAwaiter().GetResult();
return new TokenCredentials(authResult.AccessToken);
}
Result (list all website ids):
As this sample use ServicePrincipal to access your azure website resources, so you should grant associated permissions it in your subscription "Access control (IAM)" balde, such as assigning "website contributor" and "web plan contributor" to it so it has permission to manage your website resources . Hope it helps.
The new Azure.Identity library seems to be the recommended way for authenticating services within Azure. In particular the DefaultAzureCredentials() class works seamlessly in local development scenarios and in deployed code without having to make any code changes.
This is easy to use with the newer management SDKs (the ones with names like Azure.ResourceManager...) because we can just write new DefaultAzureCredentials() and pass that to the management SDK when creating a new client.
Unfortunately, the older management SDKs (the ones with names like Microsoft.Azure.Management...) do not integrate with Azure.Identity "out-of-the-box". They also do not plan to add support for Azure.Identity to these older APIs because they are instead focusing on porting everything to the newer versions.
However, not every resource in Azure has a new version management API yet and so in some cases you're stuck using the old ones. Fortunately, there is a relatively straight forward way to bridge the gap and still use Azure.Identity with those older APIs.
There's a GitHub repo which contains an example of how to achieve this. I think it's by one of the developers on the Microsoft team, but isn't officially supported by Microsoft. There is no NuGet package for it and they recommend just copying the bits you need.
I actually found that the code in that sample repo was overly complex for my needs and in my case that all I needed was this. Note, I've copied this from my F# project without testing it, so I might have made a mistake in the conversion to C#, but hopefully it's close enough that you get the idea.
class AzureIdentityFluentCredentialAdapter : AzureCredentials
{
public AzureIdentityFluentCredentialAdapter(string tenantId)
: base(default(DeviceCredentialInformation), tenantId, AzureEnvironment.AzureGlobalCloud)
{
}
public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var creds = DefaultAzureCredential() // Use the new Azure.Identity library to get access tokens
var accessToken = await creds.GetTokenAsync(
new TokenRequestContent(new [] { "https://management.azure.com/.default" }),
cancellationToken);
return await TokenCredentials(accessToken.Token)
.ProcessHttpRequestAsync(request, cancellationToken);
}
}
This example doesn't do any token caching, but for my purposes I wasn't too bothered about this. It's also hardcoded the scope that I request the token for because I knew I was only going to be using this with the Azure management API.
We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.
I have developed a .net console application which have these main characteristics :-
Integrate with SharePoint online REST API, to retrieve some list items, and modify the items fields.
Will run daily #1 am for example.
I will host this console application inside Azure Function app.
The Azure account does not have any permission on the sharepoint tenant, as the Azure account and the sharepoint online are on different domains.
so i am not sure under which account the console application will be running?
Will it runs under the current Azure account? if this is the case, then this will not work as the azure account is on different domain and does not have any permission on the sharepoint (and it shouldn't have)?
OR
I can define a service account for the Azure function app to run under it, where in this case i can define the service account to be an authorized account inside sharepoint online?
OR
i need to define the username/password inside the console application itself? i do not like to approach, as i will be exposing the password inside the console application. also changing the password for the username, means that we will need to update the console application accordingly..
so can anyone advice on this please?
Thanks
EDIT
code for managing the console application authentication :-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Client;
namespace O365SPProject
{
class Program
{
private class Configuration
{
public static string ServiceSiteUrl = "https://<tenant>.sharepoint.com";
public static string ServiceUserName = "<user>#<tenant>.onmicrosoft.com";
public static string ServicePassword = "xxxxxxxxxx";
}
static ClientContext GetonlineContext()
{
var securePassword = new SecureString();
foreach (char c in Configuration.ServicePassword)
{
securePassword.AppendChar(c);
}
var onlineCredentials = new SharePointOnlineCredentials(Configuration.ServiceUserName, securePassword);
var context = new ClientContext(Configuration.ServiceSiteUrl);
context.Credentials = onlineCredentials;
return context;
}
static void Main(string[] args)
{
var ClientContext=GetonlineContext();
Web web = clientContext.Web;
// do somethings
}
}
}
There are multiple parts to your question, so I'll answer it accordingly.
1. Which option out of the 3 you mentioned (or if there is a different better option :)), should you use to manage your configuration data/service account identity
OPTION 4 (similar to your option 2 with subtle difference):
You should take your service account identity and configuration data out of your console application completely and pass them in through "Application Settings" for your Azure Function App.
This option is similar to the option 2 you had in your question, as you keep the information outside of console app code
I can define a service account for the Azure function app to run under
it, where in this case i can define the service account to be an
authorized account inside sharepoint online?
but difference is that I am not saying that you will be able to define a service account for your Azure function app to run under (because you can't control the account that Azure function will run under, Microsoft infrastructure takes care of it), instead you will pass it to your console app as a secure configuration data and your console app will use it. More on security/encryption later while comparing the options.
I actually took your console application code from question, created a console app and used it in a timer triggered Azure function to get it working. So these steps are from a working sample. I used the "Microsoft.SharePointOnline.CSOM" nuget package in my console app, and had to upload some of the dependency dlls along with exe in order for it to run. Feel free to ask for more details on doing this part if you run into issues.
Adding Application Settings - Navigate your Azure Function App and Click on "Application Settings"
Add Settings for all items that you want to take out of your console application and control from outside. I did it for all 3 items I saw, but this is up to you.
Then change your code to use these settings. I have shown the exact code changes at the end.
OPTION 5
Registering a new application in Azure AD to represent your Azure function.
You should register a new application in your Azure AD and use this identity to access SharePoint online.
You will need to grant permissions to SharePoint online for this application (NOTE: permission assignment will not be as granular or detailed as in case of your service account approach, I'll explain more while comparing the options)
You will need to associate a certificate with your AzureAD application to help in authentication.
While authenticating to SharePoint online, you will not be directly able to use the SharePointOnlineCredentials class as in your code today, but instead send the bearer token in 'Authorization' header for the http request.
Here is blog post that walks through detailed steps involved in this option 5.
NOTE: This blog still leaves out the certificate details like password in function code at the end, which will not be ideal and you will need to move it out to App Settings or Azure Key Vault ideally.
2. Which account will the .NET console application run under and a Quick Comparison of all options
It's an arbitrary IIS App Pool account, as pointed out by #Mitch Stewart, other SO posts and is evident in the output I get for my function, it's exact value in my run came out to be "IIS APPPOOL\mawsFnPlaceholder0_v1 ". See the image at the bottom. You already have some good info shared on this, so I'll not repeat. Only thing I'll add is that this account will be controlled by the infrastructure hosting your function app and will be designed more towards taking care of isolation/other concerns in a shared infrastructure where many function apps can run, so trying to control/change it may not be the way to go right now.
Option 1 (from your question) - Giving permissions to an IIS app pool account for your SharePoint Online site, especially when you don't control the account may not be a good idea.
Option 2 (from your question) - It would have been better than the other 2 options you mentioned, but you can't really control this account.
Option 3 (from your question)- Embedding this information deep into your console application will be a maintenance issue as well as not the most secure option unless you start reading form a vault etc. Maintenance issues will remain no matter what you do because it's embedded in compiled code, which it shouldn't be.
Option 4 - This is better than previous 3 options, because it separates the concern of code from configuration and identity information, no recompilation needed for updates. Also note that whatever you store in App Settings configurations is encrypted by default (with good governance of key rotation) and is the recommended way. These values are decrypted only just before execution of your app and loaded into process memory. Look detailed discussion in this link, I have also given a small relevant excerpt below -
Provide documentation about encrypt/decrypt settings
Even with this option you could store them in a key vault and then your setting would be the URL of the key vault secret that has the actual information.
Option 5 - This option makes use of Azure AD based identity to authenticate with SharePoint Online which is good part.
It does come with some additional effort and some limitations though, so you will need to consider if these limitations are acceptable or not in your scenario:
Permissions for SharePoint online will not be as granular/detailed as a user being given permissions from inside SharePoint Users/Groups interfaces (no site/list/folder/item level specific permissions etc). In this approach, you will give the permissions as part of setting up Azure AD application and you will only get generic options like these (shown in screenshot below)
Microsoft has some well documented limitations in this scenario, which you can read here: What are the limitations when using app-only
So overall, I would suggest you choose option 4 or option 5, or a combination of both for your implementation depending on which limitations are acceptable in your scenario.
3. Code Changes to use App Settings
Just the important Change
public static string ServiceSiteUrl = Environment.GetEnvironmentVariable("ServiceSiteUrl");
public static string ServiceUserName = Environment.GetEnvironmentVariable("ServiceUserName");
public static string ServicePassword = Environment.GetEnvironmentVariable("ServicePassword");
Full Code in a working Sample (I replaced do something with reading the title and Url for SharePoint Web object):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Client;
using System.Security;
using System.Security.Principal;
namespace O365SPProject
{
class Program
{
private class Configuration
{
public static string ServiceSiteUrl = Environment.GetEnvironmentVariable("ServiceSiteUrl");
public static string ServiceUserName = Environment.GetEnvironmentVariable("ServiceUserName");
public static string ServicePassword = Environment.GetEnvironmentVariable("ServicePassword");
}
static ClientContext GetonlineContext()
{
var securePassword = new SecureString();
foreach (char c in Configuration.ServicePassword)
{
securePassword.AppendChar(c);
}
var onlineCredentials = new SharePointOnlineCredentials(Configuration.ServiceUserName, securePassword);
var context = new ClientContext(Configuration.ServiceSiteUrl);
context.Credentials = onlineCredentials;
return context;
}
static void Main(string[] args)
{
var ClientContext = GetonlineContext();
ClientContext.Load(ClientContext.Web);
ClientContext.ExecuteQuery();
Console.WriteLine("This app found web title as: {0} and URL as: {1}",
ClientContext.Web.Title, ClientContext.Web.Url);
Console.WriteLine("Console app is running with identity {0}", WindowsIdentity.GetCurrent().Name);
}
}
}
OUTPUT on executing Azure Function
The SharePoint REST API supports OAuth. Here's a promising article. Although, this might be a bit much for you intentions. Alternatively, you can try using basic auth (username + password). To guard against plain text passwords, you can store them in Azure Key Vault.
Edit
The current user of an Azure function is the identity of the IIS app pool.
Note, that I have previously written an MVC app according to Google Guidelines, by first setting up a project in Google Developers Console, providing Google Auth 2.0 authentication. Everything worked (not perfect though), but worked.
The Client ID and Client Secret I placed in Web.config. Therefore only one specific user could get his information. But I wanted any user to be able to get
his gmail message and calendar events, so we need this general Authentication model, like the one implemented in MVC 5 as Google Sign-in button. MVC 5 provides built-in functionality to sign in via Google, Facebook, Twitter, etc.
After you sign in via Google option, is it possible to access Google APIs, such as Calendar, Gmail, etc. to get my own events, gmail messages?
Example, I want to get Gmail message, I have this class:
public static MyGmail GetGmail(UserCredential credential)
{
var mygmail = new MyGmail();
var service = new GmailService(new BaseClientService.Initializer { HttpClientInitializer = credential });
var request = service.Users.Messages.List("me");
request.Q = "in:inbox is:unread";
var messages = request.Execute().Messages;
return mygmail;
}
Notice this line?
var service = new GmailService(new BaseClientService.Initializer { HttpClientInitializer = credential });
I need to pass credential to that service.
So I have this action in my controller:
public ActionResult Gmail()
{
return PartialView(GmailManager.GetGmail(result.Credential));
}
Notice this parameter: result.Credential?
That is the credentials.
That result object is coming from the Index Action:
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
if (result == null || result.Credential == null)
{
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null) return new RedirectResult(result.RedirectUri);
result.Credential.Token.ExpiresInSeconds = expiresInSeconds;
result.Credential.Token.RefreshToken = refreshToken;
}
return View();
}
Notice this line?
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
The result object is defined like this:
private static AuthorizationCodeWebApp.AuthResult result;
This is how Google OAuth 2.0 works. I am not putting here the AuthCallbackController code for simplicity. Who know what I am talking about and have seen Google Developers Tutorial on authenticating via Google OAuth 2.0, he will understand.
The idea is that after the Authentication (or when you already have the Auth token) you get that result object that has result.Credential property that I can pass as parameter to Google API Service (Gmail, Calendar or any other service) and the authenticated user can retrieve his own gamail messages or calendar events.
Now, to the question - since Authentication via 3-rd party providers such as Google is a part of MVC 5 functionality, the OWIN Katana, Microsoft Implementation (the Google button you can click and Authenticate), I dont need Google OAuth 2.0 code to authenticate a user.
That brings me to the problem - since there is no Google OAuth 2.0 implementation here, I will not be able to get that result object with Credentials, that I need to pass as parameter to Google API service.
But the user is Authenticated already via Google, so that should be double to call Google API Service, just dont know how?
Any ideas? Thanks
I guess not :)) Since there is no AuthorizationCodeWebApp.AuthResult object, that contains result.Credentials that is required to be passed as parameter to Google API Service:
var service = new GmailService(new BaseClientService.Initializer {
HttpClientInitializer = credential });
where credential is of type UserCredential (the one that comes back after Google OAuth2 authorization: AuthorizationCodeWebApp.AuthResult.Credential)