How to use auth token in Cortana Connected account scenario with graph api sdk - botframework

I am trying to follow the Cortana connected account example provided here. The default example uses live api which is working fine; however, I wanted to use Graph api sdk. Trying to use the same auth token (which is sent by Cortana as one of the entities) with Graph api is giving this error:
Microsoft.Graph.ServiceException: 'Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: -2147184118
The relevant code is:
var graphserviceClient = new GraphServiceClient(new AzureAuthenticationProvider(authAccessToken));
The AzureAuthenticationProvider class is:
public class AzureAuthenticationProvider : IAuthenticationProvider
{
string _accessToken;
public AzureAuthenticationProvider(string accessToken)
{
_accessToken = accessToken;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization", "Bearer " + _accessToken);
}
}
How can I use the auth token sent by Cortana to my bot to perform Graph queries using the Graph Sdk?

If you are trying to get a token for graph resource from Cortana, the Token URL in Connected Account should have a query parameter called resource=https://graph.microsoft.com/ Also, to verify the validity of the token, try decoding it using JWT.io. You should be able to see graph as the audience in your token.

Related

What is the botframework security model?

I am exploring the Microsoft Bot Builder SDK to create a chat bot that integrates with MS Teams. Most of the provided samples do not have any authentication mechanisms and the samples that reference OAuth seem to do so for allowing the bot to access a resource using the on-behalf-of flow. Is correct way to think of the security model is that the bot should be considered public and any non-public information accessed is done from the context of the calling user?
The Bot Framework has three kinds of authentication/authorization to consider:
Bot auth - Microsoft app ID and password
Client auth - Direct Line secret/token, or various mechanisms for other channels
User auth - OAuth cards/prompts/tokens
Unfortunately there's some inconsistency in the documentation about which is which, but I've just raised an issue about that here: https://github.com/MicrosoftDocs/bot-docs/issues/1745
In any case, there's no need to think of all bots as "public." The Bot Builder SDK authenticates both incoming messages and outgoing messages using its app ID and password. This means any unauthorized messages sent to the bot's endpoint will be rejected, and no other bot can impersonate yours.
In general you should have the user sign in if you want the bot to access secure information on the user's behalf. But since you mentioned wanting to restrict bot access to specific tenants, I can briefly explain how to do that. You can find middleware here that does it in C#, and here's a modified version of the code that I think improves on it by using a hash set instead of a dictionary:
public class TeamsTenantFilteringMiddleware : IMiddleware
{
private readonly HashSet<string> tenantMap;
public TeamsTenantFilteringMiddleware(IEnumerable<string> allowedTenantIds)
{
if (allowedTenantIds == null)
{
throw new ArgumentNullException(nameof(allowedTenantIds));
}
this.tenantMap = new HashSet<string>(allowedTenantIds);
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
{
await next(cancellationToken).ConfigureAwait(false);
return;
}
TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
string tenantId = teamsChannelData?.Tenant?.Id;
if (string.IsNullOrEmpty(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id is missing.");
}
if (!this.tenantMap.Contains(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
}
await next(cancellationToken).ConfigureAwait(false);
}
}

Xamarin OAuth2Authenticator Microsoft Account returns malformed JWT token for Azure App Service

I am building a Xamarin.Forms app to learn the framework and working on the authentication services while using an Azure App Service for an API (also new to me). And trying to authenticate against a Microsoft account (Outlook.com).
I am using the Xamarin.Auth OAuth2Authenticator class and it is returning a very peculiar, malformed JWT token. This has been driving me nuts for days and finally decided to turn to where the experts are.
I have an IAuthService interface that will be used by the platform apps to build out the authentication service
public interface IAuthService
{
Task SignInAsync(string clientId,
Uri authUrl,
Uri callbackUrl,
Action<string> tokenCallback,
Action<string> errorCallback);
}
Said service (for iOS) is built out as follows (shortened for brevity):
public class AuthService : IAuthService
{
public async Task SignInAsync(string clientId, Uri authUrl, Uri callbackUrl, Action<string> tokenCallback, Action<string> errorCallback)
{
var auth = new OAuth2Authenticator(clientId, "openid", authUrl, callbackUrl);
auth.AllowCancel = true;
var controller = auth.GetUI();
await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(controller, true);
auth.Completed += (s, e) =>
{
controller.DismissViewController(true, null);
if (e.Account != null && e.IsAuthenticated)
{
Console.WriteLine(e.Account.Properties["access_token"]);
tokenCallback?.Invoke(e.Account.Properties["access_token"]);
}
else
{
errorCallback?.Invoke("Not authenticated");
}
};
...
}
}
So I instantiate a new instance of the OAuth2Authenticator class, specifying the scope as "openid"; the MS authorize endpoint requires a scope.
When it hits the console writeline I can see a value came back... but it's not like any JWT token I have seen before.
EwB4A8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAWPAOehpFaoAKb8Kz67ZZzgzBS3KUtHGZri2sbgIJfA5xZYDv5K417HIz2P+ggUeB/gFMxRfXH1Hd1qT90bfo6skGpIc/K2vDgBoRY0VnlA9nnCyct9B2tSaNQn3hZjPOiOchmSCJxrUMILGKdKy4kxxn5qFlTXAy0hWIQjHXcwGeKXDm1w3wY6x8xsmBxNNXor9FluuUXNNTtu4iP6s424JwIiJ7HCyu6ftORXCfIlemRSv5hcHLa1MXS9vUq95lRc08S05Ek7IiUfMiAnYbrqwD7H+vheAtfDc9kYleebyxlFl6gpVKmv43DV2yYgYIqgqswO6ktJ6Gar4zmqUYUIDZgAACGWNlS0Ln+wWSAJC2apVPWWIsKPobIL0uBImdORjOWvFOnLtKhQfCnngoo1Tw1UItqo5FRj1f/KWj3if/DPgWaQx5Bf4tbqCjuuOdEkR9r/Ru1v/ccjrg2oqp0hicWwIoSaQm2JHgnrrCQ1cfcvXhAuVlAo9tKyqW/dCehdz7NRpQbNtmLvba+PjWWYEcDROJJSwTRqNTGkwiwzNhw8p/Zlf7G51F205S4vDZob1MsWythkrUJAjA6MUJy4wZ5B/8ChF7J3WRSTapjr+6mNgvgvhcflGo6GoEID24aSDL6h9QGPylk6zfghksweu9/AmSMO4BKwLDVSr04BJj1n1rfKsadUBqWUQMaXFGu+OfGbOCm6E5zLSJyO2JKbcsI468gb0/vC6FYFJOzp56GXD5brQtKNtu9urtge02kOwaGlHsK2I28BMdCRVFYJI9kEiqhqr342ZDlob7mpBCoNDk1uLLH2MPDAW9NOpq+V0bab+WawINAjl1GY/obL3zRsVNMoAszFSfdbbWS/KDbx6rw5bUPMC37s6LTbECkXHhqeqDlNQs4G9BccfiJNI5CQa+QPmaRNOBKhD2K97Z9fXmAFY155WzTPoIVKupxkPXo0zp/9vOc/HHEtMlkoUUNzxX5Q7T8awfN/7F4IfShXQKEVLaIStdx5istw7rxfuv1v/U+EMj4fmYUW9sNG/5irVyGAAOVvvPNkavLnl+NaKYysvAxYVPlrj+zJIDi5C91MmRhiTfH/Lgyq9Mlr/FaLIa/Ow6rCIjO4oBZSl9dXwLxFI4oQC
It's not encoded/decoded.
The authUrl I am using is
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
If I attempted to use the v1.0 of the authorize endpoint, the phone OS appears to throw an error to me stating Authentication Error - Invalid state from server. Possible forgery!
I have set up an Azure App Service to act as my API and if I hit my app login url directly it gives me back a well formed token which I then could use to hit the api with no problem. So that tells me that my cliend_id are correct and everything should work.
So I am doing it wrong somewhere. I don't know if I trust the OAuth2Authenticator class from Xamarin when using MS accounts. But documentation is old and/or lacking. I feel like it's going to be an easy answer but I am going cross-eyed trying to interpret MS/Xamarin/Googled documentation.

Azure AD userAssertion: Token missing scope "Directory.Read.All"

I have a Web API and a UI application that used ADAL library to call the Web API.
I already gave DELEGATED PERMISSIONS (Read directory data) for both Web API and UI application while registering the apps to Azure AD.
I have below code in Web API to save Token for log-In user,
private void ConfigureAuthentication(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }
});
}
Now in Web API controllers, I am trying to get token to access Microsoft AD Graph API using below code,
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = "test#onmicrosoft.com";
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCred = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync("https://graph.windows.net", clientCred, userAssertion);
accessToken = result.AccessToken;
The above code giving me token back but the scope values is below,
`scp: "User.Read"`
Question - Why the token not giving directory access (Directory.Read.All) as I already set Directory access?
`scp: "Directory.Read.All User.Read"`
Update:
I am missing Grant Permission for Read Directory Data under DELEGATED PERMISSIONS. After giving Grant Permission I am able to get token with scope scp: "Directory.Read.All User.Read"
If I understand correctly, you want to use Microsoft Graph API ,not Azure AD Graph API.
However, based on that the screenshot you post in this question is a v1 enpoint AAD Application, it does nothing about Microsoft Graph API which you're trying to approaching. So, whatever you changed on this application, the result shouldn be same. I suggest you register v2 enpoint Application in https://apps.dev.microsoft.com/
Here is a document which shows how to get auth tokens for using Microsoft Graph.
Hope this helps!

Openiddict guidance related to external login

I have a mobile app that talks to a backend web API (core 2.0). Presently I have the API configured to use Opendidict with Facebook integration based on the configuration listed below.
public static IServiceCollection AddAuthentication(this IServiceCollection services, AppSettings settings)
{
services.AddOpenIddict<int>(options =>
{
options.AddEntityFrameworkCoreStores<RouteManagerContext>();
options.AddMvcBinders();
options.EnableAuthorizationEndpoint("/auth/authorize");
options.EnableTokenEndpoint("/auth/token");
options.AllowAuthorizationCodeFlow();
options.AllowImplicitFlow();
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(1));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(20160));
options.DisableHttpsRequirement();
options.AddEphemeralSigningKey();
});
services.AddAuthentication()
.AddFacebook(o => { o.ClientId = settings.FacebookAppID; o.ClientSecret = settings.FacebookAppSecret; })
.AddOAuthValidation();
return services;
}
The password flow works perfectly when they want to use local account. What I'm struggling with is how to return the access/refresh token after successfully authenticating with Facebook. I have the standard account controller with ExternalLogin and ExternalLoginCallback which also works perfectly as I'm able to successfully login and get the local user account it's tied to and signed in.
In my mind, the user clicks facebook login, which calls ExternalLogincallBack, which logs in the user. After that all I want to do is return the access/refresh token just like the password flow.
When I try to use the ImplicitFlow by providing the implicit flow arguments in the redirect (/auth/authorize?...) from ExternalLoginCallback, I can get the access token, but no refresh token even if I specify the offline_scope. From what I read, it seems the implicit flow doesn't support refresh so I tried code flow.
When using the CodeFlow, I can get the code token from the redirect to "/auth/authorize" but can't figure out how to call into the token endpoint from the authorize endpoint to return the access/refresh token directly to the client app. Do I just need to return the code to the client and have them make another call to post to the token endpoint to get access/refresh tokens?
This doesn't feel correct and I'm stumped. Seems like I should be able to just return the access/refresh token after I've signed in externally just like what happens with password flow. Any help would be greatly appreciated as I've been struggling with this for several days.
[HttpGet("~/auth/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
if (!User.Identity.IsAuthenticated)
{
// If the client application request promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPrompt(OpenIdConnectConstants.Prompts.None))
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = OpenIdConnectConstants.Errors.LoginRequired,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The user is not logged in."
});
// Ask OpenIddict to return a login_required error to the client application.
return Forbid(properties, OpenIdConnectServerDefaults.AuthenticationScheme);
}
return Challenge();
}
// Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return BadRequest(new
{
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = "An internal error has occurred"
});
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null)
{
// Create a new ClaimsPrincipal containing the claims that will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme);
if (!request.IsRefreshTokenGrantType())
{
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted to allow OpenIddict to return a refresh token.
ticket.SetScopes(new[]
{
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
ticket.SetResources("RouteManagerAPI");
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them to a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in ticket.Principal.Claims)
{
// Never include the security stamp in the access and identity tokens, as it's a secret value.
if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType)
{
continue;
}
var destinations = new List<string>
{
OpenIdConnectConstants.Destinations.AccessToken
};
// Only add the iterated claim to the id_token if the corresponding scope was granted to the client application.
// The other claims will only be added to the access_token, which is encrypted when using the default format.
if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) ||
(claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) ||
(claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles)))
{
destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken);
}
claim.SetDestinations(destinations);
}
return ticket;
}
When I try to use the CodeFlow, I can get the code token but can't figure out how to call into the token endpoint from the authorize endpoint to return the access/refresh token directly to the client app. Do I just need to return the code to the client and have them make another call to post to the token endpoint to get access/refresh tokens?
That's exactly what you're supposed to do as the code flow is a 2-part process: once your mobile apps has an authorization code, it must redeem it using a simple HTTP call to the token endpoint to get an access token and a refresh token.

PlatformPlugin exception getting app-only token using MSAL in ASP.NET Core

I'm trying to create an ASP.NET Core app that uses MSAL for the client credentials flow.
I get the following exception when trying to get an access token:
NullReferenceException: Object reference not set to an instance of an object.
Microsoft.Identity.Client.Internal.PlatformPlugin.LoadPlatformSpecificAssembly()
Authentication is modeled after this sample: active-directory-dotnet-webapp-openidconnect-aspnetcore-v2
Authorization is modeled after this sample: active-directory-dotnet-daemon-v2
My app is registered with the Mail.Read Application permission. Here's the code that throws the exception:
string authority = $"https://login.microsoftonline.com/{ tenantId }/v2.0";
ConfidentialClientApplication client = new ConfidentialClientApplication(
authority,
appId,
redirectUri,
new ClientCredential(appSecret),
null);//new TokenCache());
AuthenticationResult authResult = await client.AcquireTokenForClient(
new string[] { "Mail.Read" }, null);
return authResult.Token;
Also tried with "https://graph.microsoft.com/.default" scope, as in the daemon sample.
Answering this question. I opened this as an issue on the GitHub repo. It was closed with the following response:
this problem will not occur with the next release as we will move to a single binary.

Resources