I am running an OAuth Dialog that allows user to sign in. I am looking to get this Auth token from DialogsClass.cs to my Bot.Cs class file and use it to make Graph calls.
I have tried to save token as string in local file within my dialog class and then read it back in main bot class but this solution does not seems as a right way of doing it.
AuthDialog.cs in Waterfall step:
var tokenResponse = (TokenResponse)stepContext.Result;
Expected result. Transfer this token from Dialog class to MainBot.cs class and use as string to make Graph calls.
Are you using one waterfall step to get token with OAuthPrompt and then another step to call a different class (in which you do graph api calls)?
Why can't you just pass the token to the down stream class?
If there are other steps in the middle, there are multiple ways to resolve it:
Use WaterfallStepContext Values
Save to your own UserState
Microsoft suggests not to store token in the system but make a call to oAuth prompt
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
and get latest token whenever you have to call Graph API. Once you receive the token in var tokenResponse = (TokenResponse)stepContext.Result;
you can make a call to GraphClient class which will create the Graph API client using the token in Authorization attribute.
var client = new GraphClientHelper(tokenResponse.Token);
Graph Client implementation:
public GraphClientHelper(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentNullException(nameof(token));
}
_token = token;
}
private GraphServiceClient GetAuthenticatedClient()
{
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
requestMessage =>
{
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _token);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
return Task.CompletedTask;
}));
return graphClient;
}
Once graph client is created you can make a call to the intended graph api:
await client.CreateMeeting(meetingDetails).ConfigureAwait(false);
Please refer this sample code:
Graph Sample
Related
I am just starting in with Azure and my first attempt is using the Graph client API for a simple data display. In simple terms, I want to get the Teams status of an employee and display it on a form in some graphical way.
I am trying to be as basic as can be so when I tried to download the sample I did not want the UWP project, just basic winform (console would work at the moment). I did borrow from the project and got something to compile but I get the error:
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
This is the full code and I am obviously missing something...what? This is an App that should be able to access the Graph API for a get user read and a getPresence call to show current status with the nee to have a use log in. I can see that Graph Explorer has a token and looking at postman set up there is some way to do this without a interaction, but none of the documentation is clear. I'll continue to pok at this and maybe see if I can get postman to work which might help, but behind the scene's access is not clear to me.
public partial class Form1 : Form
{
//Set the scope for API call to user.read
private string[] scopes = new string[] { "user.read" };
private const string ClientId = "my client id";
private const string Tenant = "my tenant id";
private const string Authority = "https://login.microsoftonline.com/" + Tenant;
// The MSAL Public client app
private static IPublicClientApplication PublicClientApp;
private static string MSGraphURL = "https://graph.microsoft.com/v1.0/";
private static AuthenticationResult authResult;
public Form1()
{
InitializeComponent();
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId).WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient").Build();
callMe();
}
private async void callMe()
{
// Sign-in user using MSAL and obtain an access token for MS Graph
GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(scopes);
// Call the /me endpoint of Graph
User graphUser = await graphClient.Me.Request().GetAsync();
Console.WriteLine(graphUser.Id);
var graphu2 = await graphClient.Users["my email address"].Request().GetAsync();
}
private async Task<GraphServiceClient> SignInAndInitializeGraphServiceClient(string[] scopes)
{
GraphServiceClient graphClient = new GraphServiceClient(MSGraphURL,
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await getToken(scopes));
}));
return await Task.FromResult(graphClient);
}
public async Task<string> getToken(string[] scopes)
{
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority)
.WithLogging((level, message, containsPii) =>
{
Console.WriteLine($"MSAL: {level} {message} ");
}, LogLevel.Warning, enablePiiLogging: false, enableDefaultPlatformLogging: true)
.Build();
IEnumerable<IAccount> accounts = await PublicClientApp.GetAccountsAsync().ConfigureAwait(false);
IAccount firstAccount = accounts.FirstOrDefault();
try
{
authResult = await PublicClientApp.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
Console.WriteLine($"MsalUiRequiredException: {ex.Message}");
authResult = await PublicClientApp.AcquireTokenInteractive(scopes)
.ExecuteAsync()
.ConfigureAwait(true);
}
return authResult.AccessToken;
}
Apologies but I'm going to ignore your code and break it back to something that's a lot more simple.
using Azure.Identity;
using Microsoft.Graph;
namespace StackoverflowAnswer
{
internal class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
}
static async Task MainAsync()
{
var tenantId = "YOUR_TENANT_ID";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
try
{
string[] scopes = { "https://graph.microsoft.com/.default" };
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var users = await graphClient.Users.Request().GetAsync();
foreach (var user in users)
Console.WriteLine(user.UserPrincipalName);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
A lot of the above code was taken from the following documentation as once you've authenticated, the rest of the SDK is much the same. It can be tricky in points though depending on the specific nature of what you want to do ...
https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/tokencredentials.md
This also helps ...
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider
Also make sure that you've assigned the desired API permissions to the app in the Azure Portal ...
... and also make sure you've set a client secret for your app. If you have a client ID then you've clearly already gotten that far ...
https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
Update
Now, in relation to working with the Presence API, this is a little more tricky.
Although it appears to, the Presence API doesn't support application permissions. There is an application permission for it but put simply, it doesn't work. This user voice link provides insight on that.
https://techcommunity.microsoft.com/t5/microsoft-365-developer-platform/graph-api-presence-should-support-application-permissions/idi-p/2276109
So what you need to do is apply the delegated permissions to your registered application.
Because of that, you need to use a UsernamePasswordCredential rather than a ClientSecretCredential in your code and replace it when instantiating the GraphServiceClient.
UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredential("<USERNAME>", "<PASSWORD>", tenantId, clientId);
Further to that, you'll need to make sure that the user in question has granted access to use that permission. If it was a user facing app, then they'd log in and be presented with the question to approve the permissions that you have set but because it's not, you need to go to the Enterprise Applications section in Azure AD, find your app, go to Permissions and press the Grant admin consent button for your tenant.
Someone may have a better approach than the above but it's the only way I could find to do it. It will mean if someone knows the client ID and how to authenticate, they can then execute the same API's as you.
Anyway, that will then allow you to get the presence of all users in your organisation.
I'm encountering a problem. I am using Microsoft Graph to get the current logged in user via OnBehalfOfMsGraphAuthenticationProvider.cs as seen in the following solution.
This has been working flawlessly, but I have been doing some refactoring, and suddenly I get an error when trying to execute my authContext.AcquireTokenAsync() method.
HTTP Error 502.3 - Bad Gateway
The code in question looks like this:
public async Task AuthenticateRequestAsync(HttpRequestMessage request) {
var httpContext = _httpContextAccessor.HttpContext;
//Get the access token used to call this API
string token = await httpContext.GetTokenAsync("access_token");
//We are passing an *assertion* to Azure AD about the current user
//Here we specify that assertion's type, that is a JWT Bearer token
string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
//User name is needed here only for ADAL, it is not passed to AAD
//ADAL uses it to find a token in the cache if available
var user = httpContext.User;
string userName =
user.FindFirst(ClaimTypes.Upn).Value ?? user.FindFirst(ClaimTypes.Email).Value;
var userAssertion = new UserAssertion(token, assertionType, userName);
//Construct the token cache
var cache = new DistributedTokenCache(user, _distributedCache,
_loggerFactory, _dataProtectionProvider);
var authContext = new AuthenticationContext(_configuration["AzureAd:Instance"] +
_configuration["AzureAd:TenantId"], true, cache);
var clientCredential = new ClientCredential(_configuration["AzureAd:ClientId"],
(string) _configuration["AzureAd:ClientSecret"]);
//Acquire access token
var result = await authContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential, userAssertion); //This is where it crashes
//Set the authentication header
request.Headers.Authorization = new AuthenticationHeaderValue(result.AccessTokenType, result.AccessToken);
}
I am calling it from my OrdersController:
// POST: api/Orders
[HttpPost]
public async Task<IActionResult> CreateAsync([FromBody] OrderDTO order) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var graphUser = await this.graphApiService.GetUserProfileAsync();
The refactoring has consisted of dividing my solution into two class library projects and one web project - the latter has the controllers and the React app. GraphAPiClient and the provider are located in the Core library like this:
Screenshot of architecture
So, it turns out that the problem appeared when I upgraded the package Microsoft.IdentityModel.Clients.ActiveDirectory from v3.19.8 to v4.4.1. For some reason, no versions above v3.19.8 work with my code, causing it to crash when I try to make the call to https://graph.microsoft.com, but as soon as I downgraded the problem disappeared.
Try using AcquireToken instead of AcquireTokenAsync
azureAuthenticationContext.AcquireToken
I'm trying to implement Onedrive client login by using Connect to identity providers with Web Account Manager
With this method finally I get a token using this code
private static async Task<string> RequestTokenAndSaveAccount(WebAccountProvider Provider, String Scope, String ClientID)
{
try
{
WebTokenRequest webTokenRequest = new WebTokenRequest(Provider, "wl.signin onedrive.appfolder onedrive.readwrite", ClientID);
WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);
if (webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success)
{
App.settings.onedriveStoredAccountKey = webTokenRequestResult.ResponseData[0].WebAccount.Id;
return webTokenRequestResult.ResponseData[0].Token;
}
return "";
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return "";
}
}
But I can't use the returned token to create a OnedriveClient because I need a MsaAuthenticationProvider to create the client and it creates its own token ignoring the one coming from the WebTokenRequest, and it doesn't have any method to take the prior token.
There is a way to create a OneDriveClient without going to REST Onedrive API?
Thank you
Edit:
As there are (at this time) two main versions of OneDriveSDK and those are different from each other, there are two ways to achieve this.
OneDrive.SDK 1.x
As #Brad said, an IAuthenticationProvider is needed to create the OneDriveClient.
I got the solution from https://github.com/ginach/Simple-IAuthenticationProvider-sample-for-OneDrive-SDK.
I took the SimpleAuthenticationProvider into my code, and then created the client like this
var client = new OneDriveClient(
new AppConfig(),
/* credentialCache */ null,
new Microsoft.OneDrive.Sdk.HttpProvider(),
new ServiceInfoProvider(new SimpleAuthenticationProvider { CurrentAccountSession = new Microsoft.OneDrive.Sdk.AccountSession { accessToken = AccessToken } }),
ClientType.Consumer);
client.BaseUrl = "https://api.onedrive.com/v1.0";
await client.AuthenticateAsync();
Where the accessToken is taken from the RequestTokenAndSaveAccount method.
OneDrive.SDK 2.x
For this case, the answer given by #dabox is the right solution.
Appending to Brad's answer, you can create a new AuthenticationProivder implements the IAuthenticationProivder interface in the package Microsoft.Graph.Core. And there also is a DelegateAuthenticationProvider in package Microsoft.Graph.Core which provides a Delegate interface for you. An example looks like:
OneDriveClient oneDriveClient = new OneDriveClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetAccessTokenSomeWhereAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return oneDriveClient ;
Modified based on the Microsoft Graph's asp .net example: https://github.com/microsoftgraph/aspnet-connect-sample/blob/master/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Helpers/SDKHelper.cs#L18
OneDriveClient only requires an IAuthenticationProvider, which is a pretty simplistic interface. You can create your own and implement AuthenticateRequestAsync such that it calls your RequestTokenAndSaveAccount and then adds the bearer token to the request.
I have a Xamarin based application which uses the Microsoft.OneDriveSDK nuget Package with version 1.x In this application I manage the OAuth stuff using Xamarin.Auth and thus get the access_token from that framework.
With the OneDriveSDK 1.x, I could provide this access token by redefining a few classes and then never had the API trying to fetch the token.
Now I wanted to migrate to version 2 and noticed that the previous classes got replaced and the API now uses Microsoft.Graph nuget package instead. So I had to implement the interface IAuthenticationProvider and did it like this:
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
if (!string.IsNullOrEmpty(MicrosoftLiveOAuthProvider.Instance.AccessToken))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", MicrosoftLiveOAuthProvider.Instance.AccessToken);
}
}
The code is called and properly provides the authentication token to the request headers. But once the SDK tries to use the token, I get an exception:
Exception of type 'Microsoft.Graph.ServiceException' was thrown.
Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: -2147184118
Now using google for this message always said the token is not JWT compliant and the SDK would then use it as microsoft live account token. But if this is the case, I wonder why it fails with V2 but works with V1.
Authentication is done against:
https://login.live.com/oauth20_authorize.srf
Any help is very mich appreciated!
I use a subclassed Xamarin.Auth WebRedirectAuthenticator with Microsoft.OneDriveSDK v2.0.0.
I get the initial access_token via that Xamarin.Auth subclass using a authorizeUrl: that is built via:
string GetAuthorizeUrl()
{
var requestUriStringBuilder = new StringBuilder();
requestUriStringBuilder.Append(Consts.MicrosoftAccountAuthenticationServiceUrl);
requestUriStringBuilder.AppendFormat("?{0}={1}", Consts.RedirectUriKeyName, Consts.Redirect_URI);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ClientIdKeyName, Consts.Client_ID);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ResponseTypeKeyName, Consts.TokenKeyName);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ScopeKeyName, Consts.Drive_Scopes);
return Uri.EscapeUriString(requestUriStringBuilder.ToString());
}
Once I have the access and refresh tokens, I can implement the IHttpProvider that needs passed to the OneDriveClient constructor in order to set access token in the http header:
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
SetupHttpClient();
return _httpClient.SendAsync(request);
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
SetupHttpClient();
return _httpClient.SendAsync(request, completionOption, cancellationToken);
}
HttpClient _httpClient;
void SetupHttpClient()
{
if (_httpClient == null)
{
_httpClient = new HttpClient();
var accessToken = _account.Properties["access_token"];
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
Create your OneDriveClient client using your IAuthenticationProvider and IHttpProvider objects (I just implement them on the same class that I am building all the OneDrive API calls), and every OnDrive request will use the access token from your saved Account.
Note: My IAuthenticationProvider implementation of AuthenticateRequestAsync currently does nothing, but you could do your Account setup here for a cleaner code flow.
var oneDriveClient = new OneDriveClient("https://api.onedrive.com/v1.0", this, this);
var pictureFolderItem = await oneDriveClient.Drive.Root.ItemWithPath("Pictures").Request().GetAsync();
Console.WriteLine(pictureFolderItem.Folder);
Refreshing is almost as easy, I store when the access token will expire (minus 5 minutes) and setup a timer to refresh it and re-save it to the Account. Do the same thing on app launch, if the user has an Account available and thus previously logged in, check if it is expired, refresh it, setup the background timer...
async Task<bool> GetRefreshToken(Account account)
{
// https://github.com/OneDrive/onedrive-api-docs/blob/master/auth/msa_oauth.md#step-3-get-a-new-access-token-or-refresh-token
OneDriveOAuth2Authenticator auth = OAuth2Authenticator();
var token = account.Properties["refresh_token"];
var expiresIn = await auth.RequestRefreshTokenAsync(token);
ResetRefreshTokenTimer(expiresIn);
return true;
}
I use OAuth2 with asp.net WEB API.
In my case the users will login using only their phone numbers, and they will receive SMS with verification code.
The users confirm their phone number using the verification code with this method:
[AllowAnonymous]
[Route("ConfrimPhoneNumber")]
[HttpPost]
public async Task<IHttpActionResult> VerifyPhoneNumber(VerfyPhoneModel Model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Verify phone number.
//..
//..
//Get Application User
// I need to genereate and return token from here .
}
What i want is to generate access token and refresh token for this user.
thank you for your help in advance.
I'll assume you use the default SPA template in Visual Studio. In Startup.Auth.cs you have a static property:
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
If you want to create an OAuth2 token you can use this static reference to generate the token:
Startup.OAuthOptions.AccessTokenFormat.Protect(authenticationTicket);
I want to share what I've got to make this work i collect the answer from different other posts; you can find the link in the end of this answer.
If any one has a comment or idea please share it with us.
Generate Local access Token with refresh token After user register as Response.
As Peter Hedberg Answer; We need to make OAuthOptions Puplic and static in the startup class as :
public static OAuthAuthorizationServerOptions OAuthServerOptions { get; private set; }
Then i created helper class to generate local access token and refresh
public async Task<JObject> GenerateLocalAccessToken(ApplicationUser user)
{
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
//Create the ticket then the access token
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
ticket.Properties.IssuedUtc = DateTime.UtcNow;
ticket.Properties.ExpiresUtc = DateTime.UtcNow.Add(Startup.OAuthServerOptions.AccessTokenExpireTimeSpan);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
//Create refresh token
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
//create the Token Response
JObject tokenResponse = new JObject(
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", Startup.OAuthServerOptions.AccessTokenExpireTimeSpan.TotalSeconds.ToString()),
new JProperty("refresh_token", context.Token),
new JProperty("userName", user.UserName),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
There is a problem to use basic context.SerializeTicket in SimpleRefreshTokenProvider CreateAsync method. Message from Bit Of Technology
Seems in the ReceiveAsync method, the context.DeserializeTicket is not
returning an Authentication Ticket at all in the external login case.
When I look at the context.Ticket property after that call it’s null.
Comparing that to the local login flow, the DeserializeTicket method
sets the context.Ticket property to an AuthenticationTicket. So the
mystery now is how come the DeserializeTicket behaves differently in
the two flows. The protected ticket string in the database is created
in the same CreateAsync method, differing only in that I call that
method manually in the GenerateLocalAccessTokenResponse, vs. the Owin
middlware calling it normally… And neither SerializeTicket or
DeserializeTicket throw an error…
So, you need to use Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer to searizize and deserialize ticket. It will be look like this:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
= new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
instead of:
token.ProtectedTicket = context.SerializeTicket();
And for ReceiveAsync method:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
instead of:
context.DeserializeTicket(refreshToken.ProtectedTicket);
Please refer to this Qestion and this Answer
thank you lincx and Giraffe