I would like to cancel a task using a cancellation token as described here: http://msdn.microsoft.com/en-us/library/dd997396.aspx.
The only difference is that:
I would like to start a task when I browse to a specific controller/Action, and cancel the task(tokenSource.Cancel()) to be called when I LEAVE, browse away from that specific action (page) I'm currently on. How would i do it? Thanks
I figured it out.
I wrote a custom Attribute Filter that does this. Then in the code I have the following:
tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
var taskRepository = UnityContainerSetup.Container.Resolve<ITaskRepository>();
taskRepository.GetTasksStatusAsync(siteId, tasksItem, ct); // ct is my cancellation token that I set to true => to cancel
Related
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.
In an asp.net core 3.1 web app with cookie-based authorization I have created a custom validator which executes on the cookie authorization's OnValidatePrincipal event. The validator does a few things, one of those is check in the backend if the user has been blocked. If the user has been blocked, The CookieValidatePrincipalContext.RejectPrincipal() method is executed and the user is signed out using the CookieValidatePrincipalContext.HttpContext.SignOutAsyn(...) method, as per the MS docs.
Here is the relevant code for the validator:
public static async Task ValidateAsync(CookieValidatePrincipalContext cookieValidatePrincipalContext)
{
var userPrincipal = cookieValidatePrincipalContext.Principal;
var userService = cookieValidatePrincipalContext.GetUserService();
var databaseUser = await userService.GetUserBySidAsync(userPrincipal.GetSidAsByteArray());
if (IsUserInvalidOrBlocked(databaseUser))
{
await RejectUser(cookieValidatePrincipalContext);
return;
}
else if (IsUserPrincipalOutdated(userPrincipal, databaseUser))
{
var updatedUserPrincipal = await CreateUpdatedUserPrincipal(userPrincipal, userService);
cookieValidatePrincipalContext.ReplacePrincipal(updatedUserPrincipal);
cookieValidatePrincipalContext.ShouldRenew = true;
}
}
private static bool IsUserInvalidOrBlocked(User user)
=> user is null || user.IsBlocked;
private static async Task RejectUser(CookieValidatePrincipalContext context)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
And here is the setup for the cookie-based authorization:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(co =>
{
co.LoginPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Login)}";
co.LogoutPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Logout)}";
co.ExpireTimeSpan = TimeSpan.FromDays(30);
co.Cookie.SameSite = SameSiteMode.Strict;
co.Cookie.Name = "GioBQADashboard";
co.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = UserPrincipalValidator.ValidateAsync
};
co.Validate();
});
This actually gets called and executed as expected and redirects the user to the login page when they navigate to a new page after having been blocked.
Most of the views have ajax calls to api methods that execute on a timer every 10 seconds. For those calls the credentials also get validated and the user gets signed out. However, after the user has been signed out, a popup asking for user credentials appears on the page:
If the user doesn't enter their credentials and navigate to another page, they get taken to the login page as expected.
If they do enter their credentials, they stay logged in, but their identity appears to be their windows identity...
What is going on here? What I would really want to achieve is for users to be taken to the login page for any request made after they have been signed out.
I have obviously misconfigured something, so that the cookie-based authorization doesn't work properly for ajax requests, but I cannot figure out what it is.
Or is it the Authorization attribute which does not work the way I'm expecting it to?
The code lines look good to me.
This login dialog seems to be the default one for Windows Authentication. Usually, it comes from the iisSettings within the launchSettings.json file. Within Visual Studio you'll find find the latter within your Project > Properties > launchSettings.json
There set the windowsAuthentication to false.
{
"iisSettings": {
"windowsAuthentication": false,
}
}
I see that the authorization code is inserted into the redirectUri here inside AuthorizationEndpoint.java
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("No authorization code found in the current request scope.");
}
Map<String, String> query = new LinkedHashMap<String, String>();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return append(authorizationRequest.getRedirectUri(), query, false);
}
I would love to be able to just add a snippet of code just where that query.put() is but i was unable to extend the class successfully. I couldn't figure out where/how to configure this as the auth endpoint I wanted to hit first. I also found it a bit extreme that I would need to extend the entire class just so i can add a few lines of code in order to append another query parameter to the authorization code response uri.
Thanks
I am not sure why my question received a downvote, but anyways...
Since #dur's comment basically said I could not do that. Thanks btw (I don see any way to upvote your comment, I am new to SO).
Solution: I decided to create a custom authentication filter that filtered before the login page appeared. I did some validation and passed it along for authentication. From there, I added a custom success handler for my filter and redirected to /oauth/authorize in order to jump into my intended authorization code flow.
OWIN beginner here. Please be patient...
I'm trying to build an OWIN authentication middleware which uses form posts to communicate with my external authentication provider. I've had some success with getting the authentication bits working. In other words, I can:
communicate with the remote provider through form post;
process the response returned by the remove provider
If everything is ok, I'm able to signal the default authentication provider
THis in turn gets picked up by the cookie middleware which ends up generating the authentication cookie
So far, so good. Now, what I'd like to know is how to handle a log off request. Currently, the controller will simply get the default authentication manager from the owin context and call its SingOut method. This does in fact end my current session (by removing the cookie), but it really does nothing to the existing "external" session.
So, here are my questions:
1. Is the authentication middleware also responsible for performing log off requests?
2. If that is the case, then can someone point me to some docs/examples of how it's done? I've found some links online which describe the logging in part, but haven't found anything about the log off process...
Thanks.
Luis
After some digging, I've managed to get everything working. I'll write a few tips that might help someone with similar problems in the future...
Regarding the first question, the answer is yes, you can delegate the logoff to the middleware. If you decide to do that, then your middleware handler should override the ApplyResponseGrantAsync method and check if there's a current revoke request. Here's some code that helps to illustrate the principle:
protected override async Task ApplyResponseGrantAsync() {
var revoke = Helper.LookupSignOut(Options.AuthenticationType,
Options.AuthenticationMode);
var shouldEndExternalSession = revoke != null;
if (!shouldEndExternalSession) {
return;
}
//more code here...
}
After checking if there's a revoke request, and if your external authentication provider is able to end the response through a redirect, then you can simply call the Response.Redirect method (don't forget to check for the existance of redirect - ex.: if you're using asp.net identity and MVC's automatically generated code, then the sign out will redirect you to the home page of your site).
In my scenario, things were a little more complicated because communication with my authentication provider was based of form posts (SAML2 messages with HTTP Post binding). I've started by trying to use Response.Write to inject the HTML with the autopostback form into the output buffer:
protected override async Task ApplyResponseGrantAsync() {
//previous code + setup removed
var htmlForm = BuildAndSubmitFormWithLogoutData(url,
Options.UrlInicioSessaoAutenticacaoGov);
Response.StatusCode = 200;
Response.ContentType = "text/html";
await Response.WriteAsync(htmlForm);
}
Unfortunately, it simply didn't work out. Not sure on why, but the browser insisted in redirecting the page to the URL defined by the Logoff's controller method (which was redirecting the page to its home page or '/'). I've even tried to remove the location HTTP header from within the ApplyResponseGrantAsync method, but it still ended up redirecting the user to the home page (instead of loading the predefined HTML I was writing).
I've ended up changing the redirect so that it gets handled by my middleware. Here's the final code I've ended up with in the ApplyResponseGrant method:
protected override async Task ApplyResponseGrantAsync() {
//previous code + setup removed
//setup urls for callbabk and correlation ids
var url = ...; //internal cb url that gets handled by this middleware
Response.Redirect(url);
}
This redirect forced me to change the InvokeAsync implementation so that it is now responsible for:
Checking for a new authentication session
Checking for the end of an existing authentication session (handle the logoff response from the external provider)
Checking if it should generate a new form post html message that ends the current session controlled by the external provider
Here's some pseudo code that tries to illustrate this:
public override async Task<bool> InvokeAsync() {
if (Options.InternalUrlForNewSession.HasValue &&
Options.InternalUrlForNewSession == Request.Path) {
return await HandleLoginReply(); /login response
}
if (Options.InternalUrlExternalSessionEnded.HasValue &&
Options.InternalUrlExternalSessionEnded == Request.Path) {
return await HandleLogoffReply();//logoff response
}
if (Options.InternalUrlForEndingSession.HasValue &&
Options.InternalUrlForEndingSession == Request.Path) {
return await HandleStartLogoutRequest(); //start logoff request
}
return false;
}
Yes, in the end, I've ended with an extra request, which IMO shouldn't be needed. Again, I might have missed something. If someone manages to get the ApplyResponseGrantAsync to return the auto submit post (instead of the redirect), please let me know how.
Thanks.
I'm generating a usertoken like so
public async Task GenerateCode()
{
var code = await UserManager.GenerateUserTokenAsync("heymega", new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"));
}
I then pass the same token into another action via a separate request
public async Task ValidateCode(string code)
{
var valid = await UserManager.VerifyUserTokenAsync(new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"), "heymega", code); //Returns False
}
However, the response from the VerifyUserTokenAsync method is always false.
If I were to generate the code and verify within the same action
public async Task GenerateCode()
{
var code = await UserManager.GenerateUserTokenAsync("heymega", new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"));
var valid = await UserManager.VerifyUserTokenAsync(new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"), "heymega", code); //Returns True
}
It returns true.
Why can't the Verify method verify the code in a separate request? Am I missing something obvious?
I finally figured this after pulling my hair out for hours. You need to URL encode the code and I decided to use the HttpUtility class for this.
HttpUtility.UrlEncode(code);
When it comes to verifying the code, you do not need to URL decode the code.
Having just burned 2 days on this issue, here is another reason this might be happening to you.
In your Startup.cs - ConfigureServices(IServiceCollection services) method, ensure that:
services.AddAuthentication
Appears BEFORE
services.AddIdentity
Otherwise calls to VerifyUserTokenAsync will always return false
Cannot solve this problem until haven't used this:
UserManager.VerifyUserTokenAsync(userId, AccountLockedOutPurpose, code).WithCurrentCulture<bool>();
.WithCurrentCulture() - used in all methods such as ResetPasswordAsync etc.)
In my situation I was instantiating a UserManager on demand when one was needed, as opposed to generating one per Owin context in my startup pipeline. Behavior wise, if I validated the token with the same instance of UserManager that created it, it would return true. But if I did an actual forgot password flow where the validation is in a separate request, it was always false.
Switching my setup so that a UserManager was created per owin context resolved the issue for me. Apparently there is some dependency on Owin when it comes to validating tokens.
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Not sure if OP is using .Net Core or not, but if someone comes across this and you're using dependency injection, the solution for me was to scope the UserManager as a singleton.
services.AddSingleton<UserManager<YourUserAccountModel>>();
I believe this is because when the user clicks the confirm email link in their inbox, a new UserManager instance is injected to the controller and does not have the same key that was used to generate the token to begin with. Therefore it cannot verify the token.
For me, I got the same issue. and the solution was very simple.
In my case, I add the purpose with white space like this "Email Confirmation".
the problem was solved when I removed any white space "EmailConfirmation".
bool IsTokenValed = await userManager.VerifyUserTokenAsync(user, userManager.Options.Tokens.EmailConfirmationTokenProvider, "EmailConfirmation", token);