Persistent authentication across UWP app and Azure Mobile Service - c#

Building on the example here I'm attempting to authenticate an MSA login on the client, and have it authenticate service-side as well. The difference with mine is I'm using the new WebAccount-related API's in Windows 10 instead of the now deprecated Live SDK.
So far I've got:
var provider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers");
var request = new WebTokenRequest(provider, "service::wl.basic wl.emails::DELEGATION", "none");
var result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
string token = result.ResponseData[0].Token;
//This calls my custom wrappers around the Live REST API v5 and runs successfully with this token
var acc = await LiveApi.GetLiveAccount(token);
var jtoken = new JObject
{
{"authenticationToken", token}
};
try
{
//Shouldn't this work? but raises a 401
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jtoken);
//Alternate method? Also raises a 401
//await App.MobileService.LoginWithMicrosoftAccountAsync(token);
}
}
As I mentioned in the comments, all I get are 401s.
As far as I can tell the application is configured correctly in Microsoft Account dev center:
I'm using the client ID and secret from the same app in the Azure portal.
JWT issuing is not restricted.
Redirect URL is of the format https://{appname}.azurewebsites.net/.auth/login/microsoftaccount/callback
Authentication works fine when I switch to use purely server-side authentication. i.e.
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
Any ideas? Am I missing something? Any help would be appreciated.
UPDATED:
The token I get back in the WebTokenRequestResult is 877 characters long and does not appear to be in the JWT format, with the dot (.) separators and I'm quite certain that this is the issue. The following error gets logged in service when the client calls the code above:
JWT validation failed: IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: 'EwCQAq1DBAAUGCCXc8wU/zFu9QnLdZXy+...Zz9TbuxCowNxsEPPOvXwE='.
Application: The string needs to be in compact JSON format, which is of the form: '<Base64UrlEncodedHeader>.<Base64UrlEndcodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>'..
Application: 2015-12-07T17:47:09 PID[5740] Information Sending response: 401.71 Unauthorized
What format is the token currently in? Can it be transformed to a JWT?
Still no closer to a solution, so any help is appreciated.

Anyone feel free to correct me, but it looks like RequestTokenAsync gets you an access token which you can't use to login the backend. You need an authentication token for that, and as far as I can see RequestTokenAsync doesn't get you that.
There's some info here about the tokens.

If people end up here searching for a solution for App Service Mobile, the update to MobileService. Then there is now a solution
The code replicated here is:
async Task<string> GetDataAsync()
{
try
{
return await App.MobileService.InvokeApiAsync<string>("values");
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode != HttpStatusCode.Unauthorized)
{
throw;
}
}
// Calling /.auth/refresh will update the tokens in the token store
// and will also return a new mobile authentication token.
JObject refreshJson = (JObject)await App.MobileService.InvokeApiAsync(
"/.auth/refresh",
HttpMethod.Get,
null);
string newToken = refreshJson["authenticationToken"].Value<string>();
App.MobileService.CurrentUser.MobileServiceAuthenticationToken
= newToken;
return await App.MobileService.InvokeApiAsync<string>("values");
}
Hope it saves somebody time !

Related

ASP.NET Core Identity GenerateTwoFactorTokenAsync returning empty string

App is .NET Core 2.1.
I'm implementing SMS two-factor auth in my site (already have app-based tfa), using Twilio, and everything's ready to go, with the minor problem of not being able to generate the token.
The following code is what I call when the user enters their mobile number to set up TFA in the first place. Just generating a code and texting it to them. But the call to _userManager.GenerateTwoFactorTokenAsync is returning an empty string. Which, looking at the source code, it's hard-coded to do. Useful.
I was hoping to use that so I could use the same verification process I already have in place for my app-based tfa. So what function should I be using to generate tfa tokens to be sending to users? Or am I approaching this incorrectly?
EDIT: Just from my own digging, maybe I should be using the change phone number token for this initial setup. But then the question becomes, what should I use for the actual tfa process during login?
[Authorize]
[HttpPost("send-sms")]
public async Task<IActionResult> SendSms(SMSModel input)
{
var user = await _userManager.GetUserAsync(User);
if (String.IsNullOrWhiteSpace(_smsOptions.Sid) || String.IsNullOrWhiteSpace(_smsOptions.Token)) {
ModelState.AddModelError("phoneNumber", "SMS provider not set up.");
return BadRequest(ModelState);
}
var code = await _userManager.GenerateTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider);
var message = "Your one-time verification code is: " + code;
TwilioClient.Init(_smsOptions.Sid, _smsOptions.Token);
try {
var result = await MessageResource.CreateAsync(
to: new PhoneNumber(input.phone),
from: new PhoneNumber(_smsOptions.From),
body: message
);
} catch (Exception e) {
ModelState.AddModelError("phoneNumber", e.Message);
return BadRequest(ModelState);
}
return Ok();
}
Thanks in advance!
You can use await _userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);
You can get details from docs,
how implement 2-factor auth in ASP.NET Core using sms
For anyone who comes along later, I ended up figuring it out. I just searched through the .NET core library and found the token provider PhoneNumberTokenProvider. Just passed that into the generate function instead of the Authentication one, and that seems to be working great.
Now I just need to figure out if there's a built-in way to save which kind of tfa a user is set up with, or if I need to manage that myself...
it's not work and return empty string when you try to get authenticator app token
you should to use under code to fix your problem
await _userManager.ResetAuthenticatorKeyAsync(user);
var token = await _userManager.GetAuthenticatorKeyAsync(user);
i use this code and work correctly
and you can see two factor authentication in identity source in this link

Persist sign-in Microsoft-Graph c# SDK

I'm using Microsoft Graph C#.NET SDK to access user's mail inbox. The problem is that when I do authentication the token that Microsoft sends me back is valid just for 1 hour or so and it expires so early. But it's so annoying for user to login every 1 hours just to see the outlook mail inbox. I need to make this login PERSISTENT.
Here is the code that I use:
public static async Task Run()
{
string secret = "MyDamnPrivateSecret";
PublicClientApplication clientApp = new PublicClientApplication(secret);
GraphServiceClient graphClient = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await GetTokenAsync(clientApp));
}));
//Processing mailbox ...
}
private static async Task<string> GetTokenAsync(PublicClientApplication clientApp)
{
if (string.IsNullOrEmpty(Properties.Settings.Default.token) || string.IsNullOrWhiteSpace(Properties.Settings.Default.token))
{
//need to pass scope of activity to get token
string[] Scopes = { "User.Read", "Mail.ReadWrite" };
string token = null;
AuthenticationResult authResult = await clientApp.AcquireTokenAsync(Scopes);
token = authResult.AccessToken;
Properties.Settings.Default.token = token;
Properties.Settings.Default.Save();
return token;
}
else
{
return Properties.Settings.Default.token;
}
}
Is there any way to make expiration time last longer? Or make a refresh token or something to persist login?
You'll need to request the offline_access scope to get a refresh token. If you're using an older version of MSAL, you'll need to implement and pass a token cache in the PublicClientApplication constructor which I think that MSAL will use to automatically refresh the access token. I think the newer version handles the tokenCache for you.
From the docs, this is the recommended call pattern: first try to call AcquireTokenSilentAsync, and if it fails with a MsalUiRequiredException, call AcquireTokenAsync.
private static async Task<string> GetTokenAsync(PublicClientApplication clientApp)
{
AuthenticationResult result = null;
try
{
string[] scopes = { "User.Read", "Mail.ReadWrite", "offline_access" };
// Get the token from the cache.
result = await app.AcquireTokenSilentAsync(scopes, clientApp.Users.FirstOrDefault());
return result.AccessToken;
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
// Dialog opens for user.
result = await app.AcquireTokenAsync(scopes);
return result.AccessToken;
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
}
Here's a sample for reference. https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2
I will try to clarify the issues here:
MSAL .net is build for different platforms - .net desktop, .net core, UWP, xamarin android and xamarin iOS. On some of these platforms (UWP and xamarin) we persist the token cache for you. On the others, we expect you to persist the cache. The reason is that we cannot provide token serialization logic that works well for all scenarios (e.g. ASP.NET server farms), so we expect you to do it. We provide samples and guidance around it this. Details and some reference implementations on the MSAL wiki:
The sample code provided by #Michael is ok for MSAL v1. In MSAL v2 the things are a bit different and you can find the pattern of calling is also on the MSAL wiki:
We request and store the refresh token (RT). If the auth token (AT) is expired, we will request a new one based on the RT - this will happen without user interaction. This should all be transparent to you, i.e. it should just work :). Make sure that your token cache serialization works, i.e. you get an account when performing
// perform an interactive login first
// otherwise there will be no AT / RT in the store
var accounts = await app.GetAccountsAsync();
// there should be an account that you can use
Most of our samples show how to call the Graph. See all the samples by scenario here. For your use case I recommend you check out Calling the Graph from a WPF app
Also check out #Daniel Dobalian's answer for default expiration of AT and RT:
MSAL token expires after 1 hour
In your code, AcquireTokenAsync does always trigger login.
Instead, you need to implement a token cache and use AcquireTokenSilentAsync.
For more information, please review the following link:
Microsoft Graph SDK - Login

MSAL Error message AADSTS65005 when trying to get token for accessing custom api

I downloaded the example below to get an access token from MS Graph and it worked fine. Now I changed the code to get a token from a custom web API. On apps.dev.microsoft.com I registered a client application and an the API.
Client and server registration in AD
private static async Task<AuthenticationResult> GetToken()
{
const string clientId = "185adc28-7e72-4f07-a052-651755513825";
var clientApp = new PublicClientApplication(clientId);
AuthenticationResult result = null;
string[] scopes = new string[] { "api://f69953b0-2d7f-4523-a8df-01f216b55200/Test" };
try
{
result = await clientApp.AcquireTokenAsync(scopes, "", UIBehavior.SelectAccount, string.Empty);
}
catch (Exception x)
{
if (x.Message == "User canceled authentication")
{
}
return null;
}
return result;
}
When I run the code I login to AD via the dialog en get the following exception in the debugger:
Error: Invalid client Message = "AADSTS65005: The application
'CoreWebAPIAzureADClient' asked for scope 'offline_access' that
doesn't exist on the resource. Contact the app vendor.\r\nTrace ID:
56a4b5ad-8ca1-4c41-b961-c74d84911300\r\nCorrelation ID:
a4350378-b802-4364-8464-c6fdf105cbf1\r...
Error message
Help appreciated trying for days...
For anyone still striking this problem, please read this:
https://www.andrew-best.com/posts/please-sir-can-i-have-some-auth/
You'll feel better after this guy reflects all of your frustrations, except that he works it out...
If using adal.js, for your scope you need to use
const tokenRequest = {
scopes: ["https://management.azure.com/user_impersonation"]
};
I spent a week using
const tokenRequest = {
scopes: ["user_impersonation"]
};
.. since that is the format that the graph API scopes took
As of today, the V2 Endpoint does not support API access other than the Microsoft Graph. See the limitations of the V2 app model here.
Standalone Web APIs
You can use the v2.0 endpoint to build a Web API that is secured with
OAuth 2.0. However, that Web API can receive tokens only from an
application that has the same Application ID. You cannot access a Web
API from a client that has a different Application ID. The client
won't be able to request or obtain permissions to your Web API.
For the specific scenario that you are trying to accomplish, you need to use the V1 App Model (register apps on https://portal.azure.com).
In the very near future, V2 apps will be enabled to call other APIs other than Microsoft Graph, so your scenario will be supported, but that is just not the case today. You should keep an eye out on our documentation for this update.
In your (server) application registration in AAD, you need to specify your scopes in the oauth2Permissions element.
You may already have a user_impersonation scope set. Copy that as a baseline, give it a unique GUID and value, and then AAD will let your client request an access token with your new scope.

Use google credentials to login into UWP C# app

I'm trying to make a login for a UWP app that I'm developing for a client that has a #<theircompay>.com email that uses G Suite. It doesn't have to access any user data, they just want it as an authentication so that only people that have a company email can access the app.
It would be great if they could login from within the app without having to use a web browser, and even better if it could remember them so they wouldn't have to login every single time.
I've been looking at OAuth 2.0 and several other solutions google has but can't really understand which one to use and much less how.
I looked into this answer but it doesn't seem like a good idea to ship your certificate file with your app.
So basically if this can be done, what (if any) certificates or credentials do I need to get from Google, and how would I handle them and the login through my C# code?
Edit
The app is 100% client side, no server backend
Taking a look at Google's GitHub it seems that .Net API is still not ready for UWP (however if you traverse the issues you will find that they are working on it, so it's probably a matter of time when official version is ready and this answer would be obsolete).
As I think getting simple accessToken (optionaly refresing it) to basic profile info should be sufficient for this case. Basing on available samples from Google I've build a small project (source at GitHub), that can help you.
So first of all you have to define your app at Google's developer console and obtain ClientID and ClientSecret. Once you have this you can get to coding. To obtain accessToken I will use a WebAuthenticationBroker:
string authString = "https://accounts.google.com/o/oauth2/auth?client_id=" + ClientID;
authString += "&scope=profile";
authString += $"&redirect_uri={RedirectURI}";
authString += $"&state={state}";
authString += $"&code_challenge={code_challenge}";
authString += $"&code_challenge_method={code_challenge_method}";
authString += "&response_type=code";
var receivedData = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, new Uri(authString), new Uri(ApprovalEndpoint));
switch (receivedData.ResponseStatus)
{
case WebAuthenticationStatus.Success:
await GetAccessToken(receivedData.ResponseData.Substring(receivedData.ResponseData.IndexOf(' ') + 1), state, code_verifier);
return true;
case WebAuthenticationStatus.ErrorHttp:
Debug.WriteLine($"HTTP error: {receivedData.ResponseErrorDetail}");
return false;
case WebAuthenticationStatus.UserCancel:
default:
return false;
}
If everything goes all right and user puts correct credentials, you will have to ask Google for tokens (I assume that you only want the user to put credentials once). For this purpose you have the method GetAccessToken:
// Parses URI params into a dictionary - ref: http://stackoverflow.com/a/11957114/72176
Dictionary<string, string> queryStringParams = data.Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
StringContent content = new StringContent($"code={queryStringParams["code"]}&client_secret={ClientSecret}&redirect_uri={Uri.EscapeDataString(RedirectURI)}&client_id={ClientID}&code_verifier={codeVerifier}&grant_type=authorization_code",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine("Authorization code exchange failed.");
return;
}
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString() || x.Resource == TokenTypes.RefreshToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
vault.Add(new PasswordCredential(TokenTypes.RefreshToken.ToString(), "MyApp", tokens.GetNamedString("refresh_token")));
TokenLastAccess = DateTimeOffset.UtcNow;
Once you have the tokens (I'm saving them in PasswordVault for safety), you can later then use them to authenticate without asking the user for his credentials. Note that accessToken has limited lifetime, therefore you use refreshToken to obtain a new one:
if (DateTimeOffset.UtcNow < TokenLastAccess.AddSeconds(3600))
{
// is authorized - no need to Sign In
return true;
}
else
{
string token = GetTokenFromVault(TokenTypes.RefreshToken);
if (!string.IsNullOrWhiteSpace(token))
{
StringContent content = new StringContent($"client_secret={ClientSecret}&refresh_token={token}&client_id={ClientID}&grant_type=refresh_token",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
TokenLastAccess = DateTimeOffset.UtcNow;
return true;
}
}
}
The code above is only a sample (with some shortcuts) and as mentioned above - a working version with some more error handling you will find at my GitHub. Please also note, that I haven't spend much time on this and it will surely need some more work to handle all the cases and possible problems. Though hopefully will help you to start.
Answer from Roamsz is great but didnt work for me because I found some conflicts or at least with the latest build 17134 as target, it doesn't work. Here are the problem, in his Github sample, he is using returnurl as urn:ietf:wg:oauth:2.0:oob . this is the type of url, you can't use with web application type when you create new "Create OAuth client ID" in the google or firebase console. you must use "Ios" as shown below. because web application requires http or https urls as return url.
from google doc
According to his sample he is using Client secret to obtain access token, this is not possible if you create Ios as type. because Android and Ios arent using client secret. It is perfectly described over here
client_secret The client secret obtained from the API Console. This
value is not needed for clients registered as Android, iOS, or Chrome
applications.
So you must use type as Ios, No Client Secret needed and return url is urn:ietf:wg:oauth:2.0:oob or urn:ietf:wg:oauth:2.0:oob:auto difference is that auto closes browser and returns back to the app. other one, code needs to be copied manually. I prefer to use urn:ietf:wg:oauth:2.0:oob:auto
Regarding code: please follow his github code. Just remove the Client Secret from the Access Token Request.
EDIT: it looks like I was right that even offical sample is not working after UWP version 15063, somebody created an issue on their github
https://github.com/Microsoft/Windows-universal-samples/issues/642
I'm using pretty straightforward code with Google.Apis.Oauth2.v2 Nuget package. Note, that I'm using v.1.25.0.859 of that package. I tried to update to the lastest version (1.37.0.1404), but this surprisingly doesn't work with UWP. At the same time v. 1.25.0.859 works just fine.
So, unless there's a better option, I would recommend to use a bit old, but working version of Nuget package.
This is my code:
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/User/Auth/google_client_secrets.json"),
new[] { "profile", "email" },
"me",
CancellationToken.None);
await GoogleWebAuthorizationBroker.ReauthorizeAsync(credential, CancellationToken.None);
Then you can retrieve access token from: credential.Token.AccessToken.

Azure App Services Authentication

Has anyone been able to figure out authentication using Azure App Services?
For some strange reason it is no longer handling refresh tokens like it used to in Mobile Services, the token I'm now caching expires in 1 hour, this is useless.
It's a C# UWP app, I'm using Microsoft Account as the login, I've been told to use the OneDrive API to login and retrieve the token and then use that to login to App Services, that doesn't work for me either, with an error like "you do not have permission to access the directory".
Any help is appreciated.
A solution for App Service Mobile, the update to MobileService. There should now be a solution
The code replicated here is:
async Task<string> GetDataAsync()
{
try
{
return await App.MobileService.InvokeApiAsync<string>("values");
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode != HttpStatusCode.Unauthorized)
{
throw;
}
}
// Calling /.auth/refresh will update the tokens in the token store
// and will also return a new mobile authentication token.
JObject refreshJson = (JObject)await App.MobileService.InvokeApiAsync(
"/.auth/refresh",
HttpMethod.Get,
null);
string newToken = refreshJson["authenticationToken"].Value<string>();
App.MobileService.CurrentUser.MobileServiceAuthenticationToken
= newToken;
return await App.MobileService.InvokeApiAsync<string>("values");
}
Hope it saves somebody time !

Categories