I am working with an Identity server 4 system. We are using the exact code from the MvcHybridAutomaticRefresh sample
The issue is with this code here. AutomaticTokenManagementCookieEvents.cs#L73
var response = await _service.RefreshTokenAsync(refreshToken.Value);
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
return;
}
Currently if a refesh token was revoked by the admins, or the refresh token has expired ( we do not have sliding refresh tokens enabled) Then the application will crash. I would expect it to reroute the user to the login screen.
I am i missing something in this sample that it cant handle that?
I have also posted this as a question on the issue forum #3599
current attempt
is to add The following rather where it detects the error
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
which i had hoped would log the user out. This just hangs and never goes anywhere. Its not even logging you out of the server.
Current Solution
The only thing i can find currently that remotely works is to add a catch in the api call. This is not ideal as in our actual application we have a lot of api calls this would mean making a lot of changes to our application. Isnt there a way to force a login directly from the middle wear itself?
[Authorize]
public async Task<IActionResult> CallApi()
{
try
{
var token = await HttpContext.GetTokenAsync("access_token");
var client = _httpClientFactory.CreateClient();
client.SetBearerToken(token);
var response = await client.GetStringAsync(Constants.SampleApi + "identity");
ViewBag.Json = JArray.Parse(response).ToString();
return View();
}
catch (Exception)
{
return new SignOutResult(new[] { "Cookies", "oidc" });
}
}
You can add just one row to force the middleware to perform the challenge again:
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
context.RejectPrincipal();
return;
}
Related
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
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
I have implemented ASP.NET Identity authentication and OAuth authorization according to this tutorial: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
It's currently working but i don't fully understand where the TOKEN and it's timer is stored.
This is the code that generates token:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
I would guess that token is stored in the ASP.NET Identity DB or within the hosted WEB API Application, but i don't fully understand.
the token is only generated once by the provider and it is not stored anywhere. It contains the information the application needs to authenticate the request and nothing more.
Assuming you use the Json Web Tokens or JWT, then the token is nothing more than a Json object with some properties, such as when it expires, the actual duration in seconds etc.
The token last for a configurable duration, so assuming you want to reuse that token for multiple calls then the client application will need to store somewhere in a safe manner. It could be in session for example, you could store the whole token and when you need it simply check if it's still active by looking at the duration. If it's not active anymore, you either refresh the current one you have or simply request another.
You could encapsulate all this nicely with something like this :
private TokenModel GetToken()
{
TokenModel result = null;
if (this._systemState.HasValidToken(this._currentDateTime) )
{
result = this._systemState.RetrieveUserData().TokenData;
}
else
{
try
{
result = this._portalApiWrapperBase.RequestAccessTokenData();
}
catch(Exception ex)
{
this.LastErrorMessage = ex.Message;
}
finally
{
this._systemState.AddTokenData(result);
}
}
return result;
}
In my case all this user data is stored in Session and simply retrieved from there.
I am not showing all the code here, but I basically have a state provider where I store the token once I receive it the first time. The next time I need it, if it's still valid, I return it, or go and request another if it's not. All this is hidden from the app, you just call the GetToken method and it deals with everything else.
Now, the token is supposed to be application level, it's generated based on a ClientID and CLientSecret so you could easily request another one when you need to.
The token isn't stored. The user requesting the token needs to be able to pass the token on every request in order to make an authenticated call. So it's the responsibility of the client to store the token in order to do that. (that might be in-memory for short lived sessions or on disk/in a database for longer lived sessions.
There is no need for the server to store the token, since it is passed by the client on each request. One might store it in a db themselves on the server and check if the token is there. Using that kind of mechanism allows you to revoke a token by removing it from the db. There are other ways to do that though.
By timer I guess you mean the lifetime of the token. That is checked by the framework on every request. So there is no actual timer.
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 !
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 !