When calling AcquireTokenByRefreshToken on the AuthenticationContext instance with Microsoft.IdentityModel.Clients.ActiveDirectory? - asp.net

I am developing a multi-tenant application registered on my Azure AD that consumes Office 365 apis, Graph API etc.
I followed this Microsoft sample to build my work which uses ADAL .NET library and OpenIdConnect: Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.19.0.0
In ADAL.NET, we use an AuthenticationContext instance with a custom inherited class for the TokenCache (see code the sample code here).
For each request to the authorized resources, depending on the API, we invoke one of these methods (see code below) to get the auth_token that will be put in the request Bearer parameter. Is it the correct way to do it?
We never make use of the method AcquireTokenByRefreshTokenAsync, does it mean that our application never uses the refresh_token? Does it mean that our user will have to relog after one hour? Should we implement a kind of refreshing procedure with AcquireTokenByRefreshTokenAsync in the catch statement? Can it be made without prompting anything to the end-user?
REMARK: I posted a question regarding OpenIdConnect authentication ticket lifetime. To me these two questions are unrelated but they may be.
string signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
public async Task<string> AcquireOutlook365TokenAsync()
{
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantId), new ADALTokenCache(signInUserId));
try
{
var result = await authContext.AcquireTokenSilentAsync(#"https://outlook.office365.com/",
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
catch (AdalException exception)
{
//handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
}
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
public async Task<string> AcquireAzureGraphTokenAsync()
{
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantId), new ADALTokenCache(signInUserId));
try
{
var result = await authContext.AcquireTokenSilentAsync(#"https://graph.windows.net/",
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
catch (AdalException exception)
{
//Same as other method
}
}

ADAL uses the stored refresh tokens automatically and transparently, you aren't required to perform any explicit action. AcquireTOkenByRefreshToken is in the ADAL surface for legacy reasons, and has been removed from version 3.x. More background at http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/

Related

Xamarin.Forms get new Token when session is over

I have this scenario: Xamarin.Forms App connected with Web Api 2. I make all requests and get the data i want. Now when the session token expires, i need to refresh the token but don't logout the user. The user don't need to know when token is refreshed. How to organize this, add in every request if statement when i send it and check if token expires.
This is one of my requests:
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/Profile/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
P.S I have Methods for GetToken and RefreshToken in my Api and they are working, just how to organize Refreshing ?
It really depends on what libraries are you using on your project.
But let's say you're using plain c# to handled your HTTP calls.
[OPTION 1] Polly
I can recommend you looking at Polly
It's a great library with a lot of features. You can use the Retry policy to handled expired tokens:
var _unauthorizedPolicy = Policy
.Handle<Exception>(ex => ex.StatusCode == HttpStatusCode.Unauthorized) // check here for your exception to be the right one
.RetryAsync(3, async (exception, retryCount, context) =>
{
try
{
var token = await _authService.RefreshToken();
// save the new token or whatever you need to store it
}
catch (Exception ex)
{
// RefreshToken failed, you should probably sign out the user
SignOut();
}
});
What this does is that Polly will try to execute your normal HTTP call and in case it fails and the cause is specified in Handle, then a retry mechanism is fired that will try to refresh the token and then retry your request. In the end, in case the token cannot be refreshed, you sign out the user. Of course, all this can be customized, check Polly's documentation is pretty well written.
Please note that inside Handle<T> you must put the right exception. I just used Exception as a placeholder since I'm not sure what Exception is thrown in your case.
Then you would call your method with this policy:
var result = await _unauthorizedPolicy.ExecuteAsync(() => GetProfileSetup())
And you can reuse that policy for any call, no need to create it every time.
[OPTION 2] DelegatingHandler
I will like here another StackOverflow answer:
How to Refresh a token using IHttpClientFactory
Basically you can intercept every HTTP call made via a HttpClient and refresh/add a token to your requests.
Note that that answer does not obligate you to use IHttpClientFactory, it also works for a simple HttpClient.
Also a little bit off-topic. You might want to look up for libraries to handle htt calls such as Retrofit. It will really reduce the amount of boilerplate code.

Port over existing MVC user authentication to Azure functions

I have an old web application which is using ASP.net with the build in cookie based authentication which has the standard ASP.net SQL tables for storing the users credentials.
This is currently running as an Azure web app, but I was toying with the idea of trying to go serverless as per this example creating a ReactJs SPA hosting on blob storage to try and keep costs down and also improve performance without breaking the bank.
https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/serverless/web-app
I was wondering if it is possible to port over the existing ASP.net authentication to Azure functions, to instead return a JWT (JSON Web Token) which could be passed back in the headers to handle authenticated requests.
When I have tried this in the past I have failed misserably, so I was wondering if anyone knows if it is possible?
I've seen this article, which seems to talk about Azure functions doing authentication, but with Azure AD, which I don't think is right for what I need.
https://blogs.msdn.microsoft.com/stuartleeks/2018/02/19/azure-functions-and-app-service-authentication/
The answer is kind of. What I mean by this is that you can use your existing database and many of the same libraries, but you can't port over the code configuration. The default authentication for Functions is either 1) The default API tokens or 2) one of the EasyAuth providers baked into App Services which is in the guide you linked. Currently, any other solution you'll need to setup yourself.
Assuming you go with the JWT option, you'll need to turn off all of the built-in authentication for Functions. This includes setting your HttpRequest functions to AuthorizationLevel.Anonymous.
At a basic level You'll need to create two things. A function to issue tokens, and either a DI service or a custom input binding to check them.
Issuing tokens
The Functions 2.x+ runtime is on .NET Core so I'm gong to borrow some code from this blog post that describes using JWTs with Web API. It uses System.IdentityModel.Tokens.Jwt to generate a token, which we could then return from the Function.
public SecurityToken Authenticate(string username, string password)
{
//replace with your user validation
var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
// return null if user not found
if (user == null)
return null;
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
return tokenHandler.CreateToken(tokenDescriptor);
}
Validating Tokens
There are several guides out there for validating JWT within Azure Functions. I like this one from Ben Morris: https://www.ben-morris.com/custom-token-authentication-in-azure-functions-using-bindings/ (source code). It describes authenticating with either a custom input binding or with DI. Between the two, DI is the preferred option, unless there is a specific reason you need to use a binding. Here again, its the Microsoft.IdentityModel.JsonWebTokens and System.IdentityModel.Tokens.Jwt libraries that you'll need to do the bulk of the work.
public class ExampleHttpFunction
{
private readonly IAccessTokenProvider _tokenProvider;
public ExampleHttpFunction(IAccessTokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
[FunctionName("ExampleHttpFunction")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req, ILogger log)
{
var result = _tokenProvider.ValidateToken(req);
if (result.Status == AccessTokenStatus.Valid)
{
log.LogInformation($"Request received for {result.Principal.Identity.Name}.");
return new OkResult();
}
else
{
return new UnauthorizedResult();
}
}
}

Identity Server: Access tokens/items set in AuthorizationProeperties in ExternalLoginCallback on the client

Question
I have an identity server implementation that is being used by a number of applications in test and production. I am currently working on a new feature, where the client application using the identity server can perform Azure service management REST api calls. For this, it needs a token. I can generate this token, store it and even access it in the AccountController in the identity server.
My issue is figuring out how to send this to the client. I don't think this token belongs in the claims for the user. So I tried to add it as part of AuthenticationProperties as a token, but I cannot seem to access it in the client. Should I store it in a session like this SO user did link? There is one answer to this question, but that does not seem right (I even tried it out of desperation!)
Relevant sections of code
Generate the token
var resource = "https://management.azure.com/";
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async context =>
{
// Acquire the token for the resource and save it
}
}
}
Restore it in AccountController
public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
{
string resource = "https://management.azure.com/";
// snip
result = await authContext.AcquireTokenSilentAsync(resource, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// snip
AuthenticationProperties props = null;
var tokens = new List<AuthenticationToken>();
var id_token = info.Properties.GetTokenValue("id_token");
if (id_token != null)
{
tokens.Add(new AuthenticationToken { Name = "id_token", Value = id_token });
}
if (result != null)
{
tokens.Add(new AuthenticationToken { Name = "management_token", Value = result.AccessToken });
}
if (tokens.Any())
{
props = new AuthenticationProperties();
props.StoreTokens(tokens);
}
// snip
// Can I access these "props" on the client? I even tried adding it to `Items`, no luck.
await HttpContext.Authentication.SignInAsync(user.UserId, user.DisplayName, provider, props, additionalClaims.ToArray());
}
So, my question, is this the right way go about it? If so, how do I access the authentication properties set? Or should I try saving this in the Session? If so, how do I store it in the client's session?
Any pointers would help. Thank you!
Just wanted to post an answer so that people wanting the same can benefit.
A token cache can be implemented to achieve this. This repository explains how.
Pay special attention to the AdalDistributedTokenCache linked here

Office 365 Rest Api Having issues getting access token

So far i have this.
public static async Task<OutlookServicesClient> CreateOutlookClientAsync(string capability)
{
try
{
string authority = CommonAuthority;
// Create an AuthenticationContext using this authority.
_authenticationContext = new AuthenticationContext(authority);
//See the Discovery Service Sample (https://github.com/OfficeDev/Office365-Discovery-Service-Sample)
//for an approach that improves performance by storing the discovery service information in a cache.
DiscoveryClient discoveryClient = new DiscoveryClient(
async () => await GetTokenHelperAsync(_authenticationContext, DiscoveryResourceId));
// Get the specified capability ("Contacts").
CapabilityDiscoveryResult result =
await discoveryClient.DiscoverCapabilityAsync(capability);
var client = new OutlookServicesClient(
result.ServiceEndpointUri,
async () =>
await GetTokenHelperAsync(_authenticationContext, result.ServiceResourceId));
return client;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
if (_authenticationContext != null && _authenticationContext.TokenCache != null)
_authenticationContext.TokenCache.Clear();
return null;
}
}
}
private static async Task<string> GetTokenHelperAsync(AuthenticationContext context, string resourceId)
{
string accessToken = null;
AuthenticationResult result = null;
string myId = WebConfigurationManager.AppSettings["ida:ClientID"];
string myKey = WebConfigurationManager.AppSettings["ida:Password"];
ClientCredential client = new ClientCredential(myId,myKey);
result = await context.AcquireTokenAsync(resourceId, client);
//result =context.AcquireToken(resourceId, ClientID,_returnUri);
accessToken = result.AccessToken;
return accessToken;
}
When i get to result one of two things happen if i user AcquireTokenAsync i get an error stating Application with identifier XXXX was not found in directory api.office.com otherwise if i run AcquireToken i get the login modal to pop but an error occurs indicating the request must contain client_secret .
I have no idea how to resolve this issue i suspect it may have something to do with the actual app configuration i have tried both creating my own app in Azure AD and using VS Connected Service, Has Anyone Else ran into a similar issues?
Based on the errors you're seeing, there seems to be an issue with how your app is registered. The first error usually happens when the app is not marked as multi-tenant, and you login to the app with a tenant other than the one where the app is registered.
The second error is odd. Client secret is what you're reading out of the ida:Password element and passing in the ClientCredential object.
I just put a .NET tutorial up yesterday that walks through setting this stuff up. Take a look and see if that helps get you unblocked.

LiveAuthClient broken?

It seems very much that the current version of LiveAuthClient is either broken or something in my setup/configuration is. I obtained LiveSDK version 5.4.3499.620 via Package Manager Console.
I'm developing an ASP.NET application and the problem is that the LiveAuthClient-class seems to not have the necessary members/events for authentication so it's basically unusable.
Notice that InitializeAsync is misspelled aswell.
What's wrong?
UPDATE:
I obtained another version of LiveSDK which is for ASP.NET applications but now I get the exception "Could not find key with id 1" everytime I try either InitializeSessionAsync or ExchangeAuthCodeAsync.
https://github.com/liveservices/LiveSDK-for-Windows/issues/3
I don't think this is a proper way to fix the issue but I don't have other options at the moment.
I'm a little late to the party, but since I stumbled across this trying to solve what I assume is the same problem (authenticating users with Live), I'll describe how I got it working.
First, the correct NuGet package for an ASP.NET project is LiveSDKServer.
Next, getting user info is a multi-step process:
Send the user to Live so they can authorize your app to access their data (the extent of which is determined by the "scopes" you specify)
Live redirects back to you with an access code
You then request user information using the access code
This is described fairly well in the Live SDK documentation, but I'll include my very simple working example below to put it all together. Managing tokens, user data, and exceptions is up to you.
public class HomeController : Controller
{
private const string ClientId = "your client id";
private const string ClientSecret = "your client secret";
private const string RedirectUrl = "http://yourdomain.com/home/livecallback";
[HttpGet]
public ActionResult Index()
{
// This is just a page with a link to home/signin
return View();
}
[HttpGet]
public RedirectResult SignIn()
{
// Send the user over to Live so they can authorize your application.
// Specify whatever scopes you need.
var authClient = new LiveAuthClient(ClientId, ClientSecret, RedirectUrl);
var scopes = new [] { "wl.signin", "wl.basic" };
var loginUrl = authClient.GetLoginUrl(scopes);
return Redirect(loginUrl);
}
[HttpGet]
public async Task<ActionResult> LiveCallback(string code)
{
// Get an access token using the authorization code
var authClient = new LiveAuthClient(ClientId, ClientSecret, RedirectUrl);
var exchangeResult = await authClient.ExchangeAuthCodeAsync(HttpContext);
if (exchangeResult.Status == LiveConnectSessionStatus.Connected)
{
var connectClient = new LiveConnectClient(authClient.Session);
var connectResult = await connectClient.GetAsync("me");
if (connectResult != null)
{
dynamic me = connectResult.Result;
ViewBag.Username = me.name; // <-- Access user info
}
}
return View("Index");
}
}

Resources