I am trying to use Azure Active Directory to perform login functions on my uwp app. This happens successfully however I cannot get it to refresh the token when it expires and always receive the error "Refresh failed with a 403 Forbidden error. The refresh token was revoked or expired." and so I have to bring up the login window again. I am using the version 2.1.0 and the following code to authenticate:
private async Task<bool> AuthenticateAsync(bool forceRelogon = false)
{
//string message;
bool success = false;
// Use the PasswordVault to securely store and access credentials.
PasswordVault vault = new PasswordVault();
PasswordCredential credential = null;
//Set the Auth provider
MobileServiceAuthenticationProvider provider = MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory;
MobileServiceUser user = null;
try
{
// Try to get an existing credential from the vault.
var credentials = vault.FindAllByResource(provider.ToString());
credential = credentials.FirstOrDefault();
}
catch (Exception ex)
{
// When there is no matching resource an error occurs, which we ignore.
Debug.WriteLine(ex);
}
if (credential != null && !forceRelogon)
{
// Create a user from the stored credentials.
user = new MobileServiceUser(credential.UserName);
credential.RetrievePassword();
user.MobileServiceAuthenticationToken = credential.Password;
// Set the user from the stored credentials.
App.MobileService.CurrentUser = user;
//message = string.Format($"Cached credentials for user - {user.UserId}");
// Consider adding a check to determine if the token is
// expired, as shown in this post: http://aka.ms/jww5vp.
if (RedemptionApp.ExtensionMethods.TokenExtension.IsTokenExpired(App.MobileService))
{
try
{
await App.MobileService.RefreshUserAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
success = true;
}
else
{
try
{
// Login with the identity provider.
user = await App.MobileService
.LoginAsync(provider);
// Create and store the user credentials.
if (credential != null)
vault.Remove(credential);
credential = new PasswordCredential(provider.ToString(),
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
success = true;
//message = string.Format($"You are now logged in - {user.UserId}");
}
catch (MobileServiceInvalidOperationException)
{
//message = "You must log in. Login Required";
}
}
//var dialog = new MessageDialog(message);
//dialog.Commands.Add(new UICommand("OK"));
//await dialog.ShowAsync();
return success;
}
Can anyone see something wrong with what I am doing, or need to do anything within the AAD service provider?
You might be able to get more accurate information by taking a look at the server-side application logs. Token refresh failure details will be logged there automatically. More details on application logs can be found here: https://azure.microsoft.com/en-us/documentation/articles/web-sites-enable-diagnostic-log/. I recommend setting the trace level to Informational or Verbose.
Also, if you haven't done this already, Azure AD requires a bit of extra configuration to enable refresh tokens. Specifically, you need to configure a "client secret" and enable the OpenID Connect hybrid flow. More details can be found in this blog post: https://cgillum.tech/2016/03/07/app-service-token-store/ (scroll down to the Refreshing Tokens section and see where it describes the process for AAD).
Besides what has been said about mobile app configuration, I can spot this.
You have:
// Login with the identity provider.
user = await App.MobileService.LoginAsync(provider);
It should be:
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
new Dictionary<string, string>() {{ "response_type", "code id_token" }});
Maybe this will help:
https://azure.microsoft.com/en-us/blog/mobile-apps-easy-authentication-refresh-token-support/
Related
I'm pretty new to Azure AD Graph and the authentication process. I was able to incorporate a single-sign on using the Azure AD Graph client as found in this example using a .NET MVC application: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web
My dilemma is that even though I've authenticated my session, it's still requesting that I login again to perform the actions found in the controller below:
public ActionResult Test()
{
if (Request.QueryString["reauth"] == "True")
{
//Send an OpenID Connect sign -in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
HttpContext.GetOwinContext()
.Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
// Access the Azure Active Directory Graph Client
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
// Obtain the current user's AD objectId
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
// Query and obtain the current user object from the Azure AD Graph Client
User user = (User)client.Users.
Where(u => u.ObjectId
.Equals(userObjectID, StringComparison.CurrentCultureIgnoreCase)).
ExecuteSingleAsync().
Result;
// Get the employee Id from Azure AD (via a directory extension)
IReadOnlyDictionary<string, object> extendedProperty = user.GetExtendedProperties();
object extendedProp = extendedProperty["extension_ExtensionId_employeeID"];
// Hash the employee Id
var empId = PasswordHash.ArgonHashString(extendedProp.ToString(), PasswordHash.StrengthArgon.Moderate);
// Send to the view for testing only
ViewBag.EmployeeName = user.DisplayName;
ViewBag.EmployeeEmail = user.Mail;
ViewBag.EmployeeId = empId;
return View();
}
The error I get is a:
Server Error in '/' Application
Authorization Required
With the following lines of code in the yellow box:
Line 22: if (token == null || token.IsEmpty())
Line 23: {
Line 24: throw new Exception("Authorization Required.");
Line 25: }
Line 26: return token;
Since I'm fairly new to the authentication piece, I need a little guidance on how-to obtain the current session token so I don't get this error.
I'm using the Azure AD Graph because I'm obtaining a specific directory extension in Azure that I wasn't able to obtain through Microsoft Graph (for right now and based on my current deadline).
Any advice will be helpful.
If the token is null , user needs to re-authorize . As shown in code sample , you could use try catch statement to handle the exception :
try
{
}
catch (Exception e)
{
//
// The user needs to re-authorize. Show them a message to that effect.
//
ViewBag.ErrorMessage = "AuthorizationRequired";
return View(userList);
}
Show message to user(for example , Index.cshtml in Users view folder) :
#if (ViewBag.ErrorMessage == "AuthorizationRequired")
{
<p>You have to sign-in to see Users. Click #Html.ActionLink("here", "Index", "Users", new { reauth = true }, null) to sign-in.</p>
}
If you want to directly send an OpenID Connect sign-in request to get a new set of tokens instead show error message to user , you can use :
catch (Exception e)
{
....
HttpContext.GetOwinContext()
.Authentication.Challenge(new AuthenticationProperties {RedirectUri = "/"},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
.....
}
If the user still has a valid session with Azure AD, they will not be prompted for their credentials.The OpenID Connect middleware will return to current controller after the sign-in response has been handled.
I am using the next code to get the token for Azure AD authentication
errorMessage = "";
AuthenticationResult result = null;
var context = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["login"], ConfigurationManager.AppSettings["tenantId"]),false);
ClientCredential clientCredential = new ClientCredential(ConfigurationManager.AppSettings["clientId"], ConfigurationManager.AppSettings["key"]);
try
{
result = context.AcquireToken(ConfigurationManager.AppSettings["apiEndpoint"], clientCredential);
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
errorMessage = "Temporarily Unavailable";
return null;
}
else
{
errorMessage = "Unknown Error";
return null;
}
}
string token = result.AccessToken;
var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"],token);
//string certificateString = ConfigurationManager.AppSettings["managementCertificate"];
//var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
return credential;
After that I am doing the next to create a website in Azure
using (var computeClient = new WebSiteManagementClient(credentials))
{
var result = computeClient.WebSites.IsHostnameAvailable(websiteName);
if (result.IsAvailable)
{
await computeClient.WebSites.CreateAsync(WebSpaceNames.WestEuropeWebSpace, new WebSiteCreateParameters() {
Name= websiteName,
ServerFarm= ConfigurationManager.AppSettings["servicePlanName"]
});
}
else
{
return ResultCodes.ObjectNameAlreadyUsed;
}
}
But every time I execute that I got the following error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I tried to import the management certificate as they said here:
https://www.simple-talk.com/cloud/security-and-compliance/windows-azure-management-certificates/
And also tried this one:
http://technetlibrary.com/change-windows-azure-subscription-azure-powershell/198
For importing management certificate.
Also I gave the application permissions to access management API.
Azure AD Authentication DOES NOT use the management certificate authentication.
There is a good documentation and code sample on MSDN on how to resolve your current issue.
Authenticating Service Management Requests
Looks like your application does not have permission to access the Azure API's.
Please use this link to get permissions.
After this please add permissions to access API in app permission or user permission.
I've been using Azure Mobile Services and now I created one of the new Mobile Apps via the all new Azure Portal.
While using Mobile Services it was possible to limit API access via an application key. The concept of this key no longer applies to Mobile Apps it seems.
All I need is a really lightweight protection of my services, exactly what the Application Key did. I just want to prevent that everybody out there navigates to my Azure app and messes around with my database; the App Key was perfect for those cases when you did not have anything to hide but wanted to prevent "spamming".
I see there is now Active Directory integration as an alternative but unfortunately I cannot find a guide how to move from App Key to something else.
Check this post How to configure your App Service application to use Azure Active Directory login
this authentication sample code works with UWP
private async Task AuthenticateAsync()
{
while (user == null)
{
string message=string.Empty;
var provider = "AAD";
PasswordVault vault=new PasswordVault();
PasswordCredential credential = null;
try
{
credential = vault.FindAllByResource(provider).FirstOrDefault();
}
catch (Exception)
{
//Ignore exception
}
if (credential != null)
{
// Create user
user = new MobileServiceUser(credential.UserName);
credential.RetrievePassword();
user.MobileServiceAuthenticationToken = credential.Password;
// Add user
App.MobileServiceClient.CurrentUser = user;
try
{
//intentamos obtener un elemento para determinar si nuestro cache ha experidado
await App.MobileServiceClient.GetTable<Person>().Take(1).ToListAsync();
}
catch (MobileServiceInvalidOperationException ex)
{
if (ex.Response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
//remove expired token
vault.Remove(credential);
credential = null;
continue;
}
}
}
else
{
try
{
//Login
user = await App.MobileServiceClient
.LoginAsync(provider);
//Create and store credentials
credential = new PasswordCredential(provider,
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
}
catch (MobileServiceInvalidOperationException ex)
{
message = "You must log in. Login Required";
}
}
message = string.Format("You are now logged in - {0}", user.UserId);
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
I am building a Universal Windows Platform (UWP) app that uses the Azure App Service Mobile App backend as well as the user's OneDrive account. I have 2 requirements for authentication:
If the user is logged in to their UWP device with a Microsoft account (e.g. Windows 10) then I don't want them to be presented with a login prompt (i.e. Single Sign On, re-using their Microsoft account credentials).
I want to have a single authentication event across Azure & OneDrive, i.e. the user authorises once and I re-use that token for both services.
I did this in Windows Phone 8 with an Azure Mobile Service by logging in with the Live SDK and then passing the returned token to the MobileServiceClient.LoginAsync() method, however I can't get this to work in UWP with an Azure Mobile App. When I call that same method I receive a 401 Unauthorised response.
I have associated my UWP app with the store and set up the
application at the Microsoft Account Developer Centre, including
adding the redirect URI from the Azure Mobile App.
I have set up the Azure App Service Mobile App, including adding the
Client ID & Secret from the Microsoft Account Developer Centre.
I have tried numerous ways to retrieve the token, including the
OnlineIdAuthenticator, WebAuthenticationCoreManager and
WebAuthenticationBroker. None has worked so far.
I currently use the following code in a class LiveAuthenticationService to retrieve an access token:
public async Task<bool> LoginAsync()
{
AccessToken = null;
bool success = false;
OnlineIdAuthenticator onlineIdAuthenticator = new OnlineIdAuthenticator();
EventWaitHandle waithandle = new ManualResetEvent(false);
OnlineIdServiceTicketRequest serviceTicketRequest = new OnlineIdServiceTicketRequest(scopes, "DELEGATION");
UserIdentity result = await onlineIdAuthenticator.AuthenticateUserAsync(serviceTicketRequest);
if (!string.IsNullOrWhiteSpace(result?.Tickets[0]?.Value))
{
currentUserId = result.SafeCustomerId;
AccessToken = result.Tickets[0].Value;
success = true;
waithandle.Set();
}
else
{
await logger.LogErrorAsync("Error signing in to Microsoft Live",
new Dictionary<string, string> { { "errorCode", result?.Tickets[0]?.ErrorCode.ToString() } });
}
waithandle.WaitOne(10000); //10 second timeout
return success;
}
And then this to attempt to login to my Azure Mobile App with that token, which uses LiveAuthenticationService from above:
private async Task RefreshUserIdAndAccessToken()
{
try
{
var tcs = new TaskCompletionSource<MobileServiceUser>();
var authService = new LiveAuthenticationService();
await UiDispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
try
{
await authService.LoginAsync();
var jsonAuthenticationToken = JObject.Parse(#"{""authenticationToken"": """ + authService.AccessToken + #"""}");
tcs.SetResult(await mobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jsonAuthenticationToken));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
var user = await tcs.Task;
currentUserId = user.UserId;
AccessToken = user.MobileServiceAuthenticationToken;
}
catch (Exception ex)
{
await logger.LogExceptionAsync(ex,
Constants.LOGGING_DATAKEY_REFRESHACCESSTOKENFAILURE,
currentUserId);
currentUserId = null;
AccessToken = null;
}
}
As stated this results in a 401 Unauthorised response from Azure. I have run Fiddler and the request seems to be correct, the expected authentication token is included in a JSON payload with the request.
UPDATE
One thing I can see is that the token issued by the code above is almost 900 characters long, all in the form YnElFkAAcK8bRSQab/FK+PT5n/wA4CPU..., while the token issued if I let Azure Mobile App handle the authentication, i.e. call MobileServiceClient.LoginAsync() without passing a token, is only about 350 characters long and in the form hbGciOi.eyJmdWWxsIiwiRGJn... (notice the period towards the beginning).
This issue is really causing me problems now. I can't release the app without the authentication working and I can't figure out how to fix it. Any help will be appreciated.
This was a tough one for me to solve as I was facing this problem too.
The most important part is the OnlineIdServiceTicketRequest the request should look like this:
var mobileServicesTicket = new OnlineIdServiceTicketRequest("https://yourmobileservice.azure-mobile.net/", "JWT");
Note that we are specifying your endpoint and also requesting a JWT token instead of delegation. This will get the 350ish character token you were looking for.
Here is a full code sample of what I'm doing:
public async Task<bool> LoginAsync()
{
var authenticator = new Windows.Security.Authentication.OnlineId.OnlineIdAuthenticator();
var mobileServicesTicket = new Windows.Security.Authentication.OnlineId.OnlineIdServiceTicketRequest("https://yourendpoint.azure-mobile.net/", "JWT");
var ticketRequests = new List<OnlineIdServiceTicketRequest>() { mobileServicesTicket };
var authResult = await authenticator.AuthenticateUserAsync(ticketRequests, CredentialPromptType.PromptIfNeeded);
if ((authResult.Tickets.Count == 1) && (authResult.Tickets[0].ErrorCode == 0))
{
var accessToken = authResult.Tickets[0];
var res = await _mobileServiceClient.LoginWithMicrosoftAccountAsync(accessToken.Value);
return true;
}
else
{
return false;
}
}
_mobileServiceClient is injected into the class and is a reference to Microsoft.WindowsAzure.MobileServices.MobileServiceClient object within the WindowsAzure.MobileServices library.
I actually ended up writing a blog article about this problem here http://jshapland.com/single-sign-on-with-azure-mobile-services-in-a-uwp-app/
I may be handling this totally incorrect, but I am using OpenID with MS Azure to authentication my users, then I check to make sure the user has a user account in the notifications of the OpenID middleware, if the user is not found, I am throwing a security exception. How do I return a You do not have access to this applicaiton type page. Am I just missing the hook?
Here is the example:
https://gist.github.com/phillipsj/3200ddda158eddac74ca
You can use try...catch inside the notifications, something along these lines:
SecurityTokenValidated = (context) =>
{
try
{
// retriever caller data from the incoming principal
var username = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value.Split('#')[0];
var database = DependencyResolver.Current.GetService(typeof (IDatabase)) as IDatabase;
var employee = database.Query(new GetEmployeeByUsername(username));
if (employee == null)
{
throw new SecurityTokenValidationException();
}
// I add my custom claims here
context.AuthenticationTicket.Identity.AddClaims(claims);
return Task.FromResult(0);
}
catch (SecurityTokenValidationException ex)
{
context.HandleResponse(); // This will skip executing rest of the code in the middleware
context.Response.Redirect(....);
return Task.FromResult(0);
}
}