How to get access token to team's Bot activity handlers - microsoft-graph-api

When we have a dialog we
private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse != null)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken);
return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken);
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
how to get the token to the teams bot activity handler , just like the
var tokenResponse = (TokenResponse)stepContext.Result
protected override async Task OnTeamsChannelRenamedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{channelInfo.Name} is the new Channel name");
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}

how to get the token to the teams bot activity handler , just like the
var tokenResponse = (TokenResponse)stepContext.Result
You don't. Once you obtain the access token, you store it into a variable you can access, such as in your root dialog context's state.
Check out documentation on DialogContext, and its State property for managing a Dialog's memory and memory scopes, which is designed specifically for cases like yours where you need to access a token across turns.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.dialogs.dialogcontext.state?view=botbuilder-dotnet-stable#microsoft-bot-builder-dialogs-dialogcontext-state
Also, if you don't have a specific reason to not be using Bot Framework Composer, I highly suggest you check out the Composer instead of trying to handle Auth and everything on your own. Composer comes with all of these dialogs including Auth pre-built, and any custom dialogs or function you want that you cannot achieve within the Composer can be added to the bot can be achieved through Middleware / Custom actions. I say this because if you want to store dialog states consistently you would also need to store the dialog state in some sort of database so you can persist tokens; Composer already handles all of this for you.

Related

MSAL.NET redirect loop when using graphApi in MVC & blazor with multiple instances

I have created a blazor component that aims to simplify managing users and group of an enterprise application in my ASP.NET MVC website. When I run the code locally, everything works just fine. However, when I deploy my code on the dev environment (in AKS) the code only works if I run one replica.
When I use multiple instances and I try to access the page that calls my blazor component, the page ends up in a redirect loop, and finally shows the Microsoft login interface with an error mentioning that the login was not valid.
This is how my code looks like:
# program.cs
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
var cacheOptions = builder.Configuration.GetSection("AzureTableStorageCacheOptions").Get<AzureTableStorageCacheOptions>();
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddDistributedTokenCaches();
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24);
});
builder.Services.AddDistributedAzureTableStorageCache(options =>
{
options.ConnectionString = cacheOptions.ConnectionString;
options.TableName = cacheOptions.TableName;
options.PartitionKey = cacheOptions.PartitionKey;
options.CreateTableIfNotExists = true;
options.ExpiredItemsDeletionInterval = TimeSpan.FromHours(24);
});
builder.Services.AddSession();
...
# The controller that calls the blazor component
[AuthorizeForScopes(Scopes = new[] { "Application.ReadWrite.All", "Directory.Read.All", "Directory.ReadWrite.All" })]
public async Task<IActionResult> UserManagement()
{
string[] scopes = new string[] { "Application.ReadWrite.All", "Directory.Read.All", "Directory.ReadWrite.All" };
try
{
await _tokenAcquisition
.GetAccessTokenForUserAsync(scopes)
.ConfigureAwait(false);
}
catch (Exception ex)
{
_telemetryClient.TrackException(ex);
}
return View();
}
And this is what happens:
If the page loads, I can see this exception in the pod logs:
What am I doing wrong?
The tenant actually needs to provide admin consent to your web API for the scopes you want to use for replicas for the token taken from cache.
Also when AuthorizeForScopes attribute is specified with scopes ,this needs the exact scopes that is required by that api. MsalUiRequiredException gets thrown in case of incorrect scopes for that api and results in a challenge to user.
This error may also occur even when the acquiretokensilent call will not have a valid cookie anymore for authentication in cache .Please check how acquiretokensilent call works from here in msal net acquire token silently | microsoft docs
When valid scopes are given , please make sure the permissions are granted consent by the admin directly from portal or during user login authentication.
Also as a work around try to use use httpContextAccessor to access
token after authentication .
Reference: c# - Error : No account or login hint was passed to the AcquireTokenSilent call - Stack Overflow
So, the culprit was:
#my controller
await _tokenAcquisition
.GetAccessTokenForUserAsync(scopes)
.ConfigureAwait(false);
Which we were using initially to reauthenticate the graph api component when we were using InMemoryCache.
There is no need to get the access token again when using DistributedTokenCache, and actually that was causing the token to get saved / invalidated in an infinite loop.
Also, in my blazor component, I had to do use the consent handler to force a login:
private async Task<ServicePrincipal> GetPrincipal(AzureAdConfiguration addConfiguration)
{
try
{
return await GraphClient.ServicePrincipals[addConfiguration.PrincipalId].Request()
.Select("id,appRoles, appId")
.GetAsync();
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
throw;
}
}

How do I connect to Exchange Online using OAuth 2.0 in MailKit?

I have a web application that sends e-mails to users via Exchange Online (Office365) using MailKit and Basic Authentication. Our company is MS partner and therefor is obligated to turn off Basic Authentication for our services by the end of february 2020.
So, I want to use OAuth 2.0 to connect to Exchange Online, similar to this example. In fact, there might be a solution available according to this answer but I'm unable to find anything about it.
Right now I'm playing around with MS Identity Platform v2.0 but I'm unable to figure out how to do it.
Any help would be appriciated.
UPDATE 1
I do not want to send mails on behalf of signed-in users but instead there is a single Office365 user account that shall be used to send mails (notifications and so on) to others.
UPDATE 2
I managed to get a little closer to what I want to do using Microsoft Graph SDK and the Username/Password Provider.
Our user account requires multifactor-authentication and therefor I get an error when using the user's password since I cannot satisfy the second factor. When I'm using an app-password authentication fails because of incorrect password.
UPDATE 3
I switched to mail relaying for now. But I will update this question if I'll ever find an answer to it.
Using the Microsoft.Identity.Client you can generate a token and pass though then authentication using that.
I spotted the below for IMAP, POP3 and SMTP so adapted for my project to get a working solution. Although the example show the interactive method, where as I am was trying to use the the client credentials flow with an app secret.
MailKit - Using OAuth2 With Exchange (IMAP, POP3 or SMTP)
Microsoft - Authenticate an IMAP, POP or SMTP connection using OAuth
From #hB0 comment
Setting up Service Principles via client credentials grant flow (non-interactive)
https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-client-credentials-flow-support-for-pop-and/ba-p/3562963
My choice would be to look into Microsoft Graph API . It is a single endpoint for all Microsoft services including Email. Email specific endpoints document is here
Microsoft provides SDK in different languages to develop client applications using Graph API.
At a high level you would need to do the following.
i) Register an application in Azure Active Directory. See here
ii) Use the Oauth2 'authorization code grant' flow to get a refresh token . See here
iii) Exchange the refresh token for an access token and use the access token to call Microsoft Graph API.
iv) You also need to store the refresh token , if you have use cases where you application needs to perform actions even if the user is offline. In this case make sure you include scope 'offline' in step ii)
I would suggest looking into DotNetOpenAuth or a similar library and reading their samples. You'll probably need to know the Windows Live URLs to use for this if the DotNetOpenAuth library doesn't have them built-in.
Samples can be found here: https://github.com/DotNetOpenAuth/DotNetOpenAuth.Samples
I know this is an old post but with Microsoft progressively rolling modern authentication on all Office 365 tenants. Here's what I cobbled together.
I haven't worked with MFA setups.
I use it to fetch attachments via POP3 from automated mails coming in a mailbox of our tenant, the app runs from a scheduled task so it needs to be able to run without interaction.
First, you need to get the TenantID and ClientID the tenant admin gets when registering an app on the tenant. Credits to #jstedfast for the bootstrap doc to use those informations elegantly.
Then, setup a cache for the authentication token (following this article and the wiki page linked to it).
Then handle the logic whether to use interactive or silent authentication and avoid prompting for sign-in everytime. (straight copy/paste from documentation, but rather than leaving a link that might break...)
I wrapped it all together in a function that I call later to handle the authentication.
private static async Task<AuthenticationResult> GetMSALTokenAsync()
{
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/POP.AccessAsUser.All"
};
var options = new PublicClientApplicationOptions
{
ClientId = Settings.Default.MSALClientId,
TenantId = Settings.Default.MSALTenantId,
RedirectUri = Settings.Default.MSALRedirectURI
};
var storageProperties = new StorageCreationPropertiesBuilder(
Settings.Default.MSALTokenCache,
MsalCacheHelper.UserRootDirectory)
.Build();
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
AuthenticationResult authToken;
try
{
authToken = await publicClientApplication.AcquireTokenSilent(scopes, accounts.First(o => o.Username == Settings.Default.LoginPop)).ExecuteAsync();
return authToken;
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
switch (ex.Classification)
{
case UiRequiredExceptionClassification.None:
break;
case UiRequiredExceptionClassification.MessageOnly:
// You might want to call AcquireTokenInteractive(). Azure AD will show a message
// that explains the condition. AcquireTokenInteractively() will return UserCanceled error
// after the user reads the message and closes the window. The calling application may choose
// to hide features or data that result in message_only if the user is unlikely to benefit
// from the message
try
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
{
// Do nothing. The user has seen the message
}
break;
case UiRequiredExceptionClassification.BasicAction:
// Call AcquireTokenInteractive() so that the user can, for instance accept terms
// and conditions
case UiRequiredExceptionClassification.AdditionalAction:
// You might want to call AcquireTokenInteractive() to show a message that explains the remedial action.
// The calling application may choose to hide flows that require additional_action if the user
// is unlikely to complete the remedial action (even if this means a degraded experience)
case UiRequiredExceptionClassification.ConsentRequired:
// Call AcquireTokenInteractive() for user to give consent.
case UiRequiredExceptionClassification.UserPasswordExpired:
// Call AcquireTokenInteractive() so that user can reset their password
case UiRequiredExceptionClassification.PromptNeverFailed:
// You used WithPrompt(Prompt.Never) and this failed
case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
default:
// May be resolved by user interaction during the interactive authentication flow.
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
}
catch (InvalidOperationException)
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
log.Error("Authentication failed.");
return null;
}
Then you can just roll on with the actual logic to do your stuff with the Exchange server.
private static async Task PopDownloadAsync()
{
using (var client = new Pop3Client())
{
try
{
await client.ConnectAsync(Settings.Default.SrvPop, 995, SecureSocketOptions.SslOnConnect);
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
try
{
var result = await GetMSALTokenAsync();
if (result != null)
{
var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
await client.AuthenticateAsync(oauth2);
}
else
{
throw new AuthenticationException("Something went wrong during authentication...");
}
}
catch (AuthenticationException ex)
{
// do stuff
return;
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
if (client.Capabilities.HasFlag(Pop3Capabilities.UIDL))
{
try
{
// do stuff
}
catch (Pop3CommandException ex)
{
// do stuff
}
catch (Pop3ProtocolException ex)
{
// do stuff
if (!client.IsConnected)
return;
}
catch (Exception e)
{
// do stuff
return;
}
}
if (client.IsConnected)
{
await client.DisconnectAsync(true);
}
}
}

How to properly log in a user from Owin Middleware

I'm making my own system to authenticate jwt tokens in certain scenarios.
When I have properly validated the token, I have
var userIdentity = await user.CreateIdentityAsync(DefaultAuthenticationTypes.ExternalBearer);
owinContext.Authentication.User = new System.Security.Claims.ClaimsPrincipal(userIdentity);
owinContext.Authentication.SignIn(userIdentity);
System.Web.HttpContext.Current.User = owinContext.Authentication.User;
await next()
but that doesn't seem to fix authentication which still fails at - I believe - the Asp.Net Mvc level. Because I know it uses HttpContext I try adding this before calling next()
HttpContext.Current.User = new GenericPrincipal(userIdentity, new string[0]);
This gets me further along but I still seem to be getting an an authorization error it would seem (by searching source for the message that I get and where its used) to be coming from the Web Api [Authorize] attribute.
I'm hitting a wall as far as tracing through the .net source code. The only way I should be getting this message is if IsAuthorized returns false. But there are no roles nor users specified (it's just plain [Authorize]) and before heading off to the next() I can stop the debugger and check that yes there is a user identity, and yes it IsAuthorized.
I've overridden the AuthorizeAttribute so as to place breakpoints and can see that by the time it is called however, my actionContext is associated with a completely different identity with IsAuthorized == false. Which in turn makes me wonder if I'm signing in the user identity wrong
So... am I doing this correctly? What should I be doing?
I have never undertstood why but in my case, i have need to valid the ticket after signing in:
var userIdentity = await user.CreateIdentityAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignIn(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(userIdentity, null);
ctx.Validated(ticket);
Edit
I'm not really in the same context. In my case, I have a custom authentication provider inheriting of Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationProvider :
public class CustomBearerAuthenticationProvider:OAuthBearerAuthenticationProvider
{
public CustomBearerAuthenticationProvider() : base()
{
this.OnValidateIdentity = (context) => Task.Run(() =>
{
var identity = this.CreateApplicationIdentity(user);
context.OwinContext.Authentication.SignIn(identity);
AuthenticationTicket ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
});
}
}
context is of type : Microsoft.Owin.Security.OAuth.OAuthValidateIdentityContext

How to implement OAuth2 for a single tool, without using it as my application's authorization solution

I currently have a MVC site, in .NET Core, backed by a public API. My users must log in (there are no [Anonymous] controllers), and authentication is already successfully being done using the DotNetCore.Authentication provider. All that is well and good.
What I'm now trying to do (by user request) is implement functionality for a user to read and view their Outlook 365 calendar on a page within my site. It doesn't seem too hard on the surface... all I have to do is have them authenticate through microsoftonline with my registered app, and then -- once they have given approval -- redirect back to my app to view their calendar events that I am now able to pull (probably using Graph).
In principle that seems really easy and straightforward. My confusion comes from not being able to implement authentication for a single controller, and not for the entire site. All of the OAuth2 (or OpenID, or OWIN, or whatever your flavor) examples I can find online -- of which there are countless dozens -- all want to use the authorization to control the User.Identity for the whole site. I don't want to change my sitewide authentication protocol; I don't want to add anything to Startup.cs; I don't want anything to scope outside of the one single controller.
tldr; Is there a way to just call https://login.microsoftonline.com/common/oauth2/v2.0/authorize (or facebook, or google, or whatever), and get back a code or token that I can use for that user on that area of the site, and not have it take over the authentication that is already in place for the rest of the site?
For anybody else who is looking for this answer, I've figured out (after much trial and error) how to authenticate for a single user just for a short time, without using middleware that authenticates for the entire application.
public async Task<IActionResult> OfficeRedirectMethod()
{
Uri loginRedirectUri = new Uri(Url.Action(nameof(OfficeAuthorize), "MyApp", null, Request.Scheme));
var azureADAuthority = #"https://login.microsoftonline.com/common";
// Generate the parameterized URL for Azure login.
var authContext = GetProviderContext();
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(_scopes, loginRedirectUri.ToString(), null, null, null, azureADAuthority);
// Redirect the browser to the login page, then come back to the Authorize method below.
return Redirect(authUri.ToString());
}
public async Task<IActionResult> OfficeAuthorize()
{
var code = Request.Query["code"].ToString();
try
{
// Trade the code for a token.
var authContext = GetProviderContext();
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(code, _scopes);
// do whatever with the authResult, here
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
return View();
}
public ConfidentialClientApplication GetContext()
{
var clientId = "OfficeClientId;
var clientSecret = "OfficeClientSecret";
var loginRedirectUri = new Uri(#"MyRedirectUri");
TokenCache tokenCache = new MSALSessionCache().GetMsalCacheInstance();
return new ConfidentialClientApplication(
clientId,
loginRedirectUri.ToString(),
new ClientCredential(clientSecret),
tokenCache,
null);
}
I don't know if that will ever be helpful to anybody but me; I just know that it's a problem that doesn't seem to be easily solved by a quick search.

Using OAuth2 refresh tokens in an ASPMVC application

Scenario
I am using the OWIN cookie authentication middleware to protected my site as follows
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = new TimeSpan(0, 20, 0),
SlidingExpiration = true
});
}
On login, I use the resource owner password flow to call my token service and retrieve both an access and refresh token.
I then add the refresh token, access token and the time the access token expires to my claims and then call the following to to persist this information to my authentication cookie.
HttpContext
.GetOwinContext()
.Authentication
.SignIn(claimsIdentityWithTokenAndExpiresAtClaim);
Then before calling any service, I can retrieve the access token from my current claims and associate it with the service call.
Problem
Before calling any service, I should really check if the access token has expired and if so use the refresh token to get a new one. Once I have a new access token, I can call the service, however I then need to persist a new authentication cookie with the new access token, refresh token and expiry time.
Is there any nice way to do this transparently to the caller of the service?
Attempted solutions
1) Check before calling every service
[Authorize]
public async Task<ActionResult> CallService(ClaimsIdentity claimsIdentity)
{
var accessToken = GetAccessToken();
var service = new Service(accessToken).DoSomething();
}
private string GetAccessToken(ClaimsIdentity claimsIdentity) {
if (claimsIdentity.HasAccessTokenExpired())
{
// call sts, get new tokens, create new identity with tokens
var newClaimsIdentity = ...
HttpContext
.GetOwinContext()
.Authentication
.SignIn(newClaimsIdentity);
return newClaimsIdentity;
} else {
return claimsIdentity.AccessToken();
}
}
This would work, but it's not sustainable. Also I could not longer use dependency injection to inject my services as the service needs the access token at call time and not construction time.
2) Use some kind of service factory
Before create the service with its access token, it would perform the refresh if needed. The issue it that I'm not sure how I can get the factory to return both a service and also set the cookie within the implementation in a nice way.
3) Do it in a action filter instead.
The thinking is that the session cookie has a 20 minutes sliding expiry. On ever page request, I can check if the access token is more than halfway through it's expiry (ie. if the access token has an expiry of an hour, check to see if it has less than 30 minutes to expiry). If so, perform the refresh. The services can rely on the access token not being expired. Lets say you hit the page just before the 30 minutes expiry and stayed on the page for 30 minutes, the assumption is the session timeout (20 minutes idle) will kick in before you call the service and you wil be logged off.
4) Do nothing and catch the exception from calling a service with an expired token
I couldn't figure out a nice way to get a new token and retry the service call again without having to worry about side effects etc. Plus it would be nicer to check for expiration first, rather than wait for the time it takes the service to fail.
Neither of these solutions are particularly elegant. How are others handling this?
Update:
I spent some time looking in to various options on how to implement this efficiently at the server side with your current setup.
There are multiple ways (like Custom-Middleware, AuthenticationFilter, AuthorizationFilter or ActionFilter) to achieve this on the server side. But, looking at these options I would lean towards AuthroziationFilter. The reason are:
AuthroziationFilters gets executed after AuthenticationFilters. So, it is early in the pipe line that you can make a decision of whether to get a new token or not based on expiry time. Also, we can be sure that the user is authenticated.
The scenario we are dealing with is about access_token which is related to authorization than the authentication.
With filters we have the advantage of selectively using it with actions that are explicitly decorated with that filter unlike the custom middleware which gets executed with every request. This is useful as there will be cases where you do not want to get a refreshed token (since the current one is still valid as we are getting new token well before the expiration) when you are not calling any service.
Actionfilters are called little late in the pipeline also we do not have a case for after executing method in an action filter.
Here is a question from Stackoverflow that has some nice details on how to implement an AuthorizationFilter with dependency injection.
Coming to attaching the Authorization header to the service:
This happens inside your action method. By this time you are sure that the token is valid. So I would create an abstract base class that instantiates a HttpClient class and sets the authorization header. The service class implements that base class and uses the HttpClient to call the web service. This approach is clean as consumers of your setup do not have to know how and when you are getting and attaching the token to the outgoing request for web service. Also, you are getting and attaching the refreshed access_token only when you are calling the web service.
Here is some sample code (please note that I haven't fully tested this code, this is to give you an idea of how to implement):
public class MyAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
private const string AuthTokenKey = "Authorization";
public void OnAuthorization(AuthorizationContext filterContext)
{
var accessToken = string.Empty;
var bearerToken = filterContext.HttpContext.Request.Headers[AuthTokenKey];
if (!string.IsNullOrWhiteSpace(bearerToken) && bearerToken.Trim().Length > 7)
{
accessToken = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
}
if (string.IsNullOrWhiteSpace(accessToken))
{
// Handle unauthorized result Unauthorized!
filterContext.Result = new HttpUnauthorizedResult();
}
// call sts, get new token based on the expiration time. The grace time before which you want to
//get new token can be based on your requirement. assign it to accessToken
//Remove the existing token and re-add it
filterContext.HttpContext.Request.Headers.Remove(AuthTokenKey);
filterContext.HttpContext.Request.Headers[AuthTokenKey] = $"Bearer {accessToken}";
}
}
public abstract class ServiceBase
{
protected readonly HttpClient Client;
protected ServiceBase()
{
var accessToken = HttpContext.Current.Request.Headers["Authorization"];
Client = new HttpClient();
Client.DefaultRequestHeaders.Add("Authorization", accessToken);
}
}
public class Service : ServiceBase
{
public async Task<string> TestGet()
{
return await Client.GetStringAsync("www.google.com");
}
}
public class TestController : Controller
{
[Authorize]
public async Task<ActionResult> CallService()
{
var service = new Service();
var testData = await service.TestGet();
return Content(testData);
}
}
Please note that using the Client Credentials flow from OAuth 2.0 spec is the approach we need to take when calling an API. Also, the JavaScript solution feels more elegant for me. But, I am sure you have requirements that might be forcing you to do it the way you want. Please let me know if you have any questions are comments. Thank you.
Adding access token, refresh token and expires at to the claims and passing it to the following service may not be a good solution. Claims are more suited for identifying the user information/ authorization information. Also, the OpenId spec specifies that the access token should be sent as part of the authorization header only. We should deal with the problem of expired/ expiring tokens in a different way.
At the client, you can automate the process of getting a new access token well before its expiration using this great Javascript library oidc-client. Now you send this new and valid access token as part of your headers to the server and the server will pass it to the following APIs. As a precaution, you can use the same library to validate the expiration time of the token before sending it to the server. This is much cleaner and better solution in my opinion. There are options to silently update the token without the user noticing it. The library uses a an iframe under the hood to update the token. Here is a link for a video in which the author of the library Brock Allen explains the same concepts. The implementation of this functionality is very straightforward. Examples of how the library can be used is here. The JS call we are interested in would look like:
var settings = {
authority: 'http://localhost:5000/oidc',
client_id: 'js.tokenmanager',
redirect_uri: 'http://localhost:5000/user-manager-sample.html',
post_logout_redirect_uri: 'http://localhost:5000/user-manager-sample.html',
response_type: 'id_token token',
scope: 'openid email roles',
popup_redirect_uri:'http://localhost:5000/user-manager-sample-popup.html',
silent_redirect_uri:'http://localhost:5000/user-manager-sample-silent.html',
automaticSilentRenew:true,
filterProtocolClaims: true,
loadUserInfo: true
};
var mgr = new Oidc.UserManager(settings);
function iframeSignin() {
mgr.signinSilent({data:'some data'}).then(function(user) {
log("signed in", user);
}).catch(function(err) {
log(err);
});
}
The mgr is an instance of
FYI, we can achieve similar functionality at the server by building a custom middleware and using it as part of the request flow in a MessageHandler. Please let me know if you have any questions.
Thanks,
Soma.

Resources