Azure AD : how to redirect to sign in page by code manually - azure

I followed this official sample to integrate Azure AD authentication into my ASP.NET Core application. Everything is working well, and I can call the MS Graph API successfully.
The problem is that after I signed in, I kept the browser opening and then I shutdown my application (simulate the server crashed), then I run the application again, then refreshing the page, I got an exception as shown in this screenshot:
I debugged the code, and I found that the request went into my controller (I had [Authorize] on my controller so it should redirect to sign in page when app found the request doesn't be authenticated) and the exception appeared when run
var me = await _graphClient.Me.Request().GetAsync();
This means the app thinks the request is authenticated.
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphClient;
public HomeController(GraphServiceClient graphClient, ILogger<HomeController> logger)
{
_logger = logger;
_graphClient = graphClient;
}
public async Task<IActionResult> IndexAsync()
{
var me = await _graphClient.Me.Request().GetAsync();
ViewBag.Myname = me.DisplayName;
return View();
}
}
The reason for why the app not redirected to sign in page is that the browser stored cookie, after removing the cookie manually, the exception wouldn't appear and app would redirect to sign in page.
I think I need a global filter to handle the exception, by removing the cookie (I'm not sure if it can remove the cookie) and then redirect to sign in page manually, but I failed to find any document to describe how to redirect to sign in page manually by code.
Any document or code sample is appreciated.
By the way, should we put effort on this kind of scenario?

• As per my understanding on your query regarding the redirection to sign in page manually through code, I would suggest you to please go through the below code which suggests the same mechanism as you are expecting, i.e., delete the cookie from browser memory for the session signed in and redirect to the sign in page again for login to a new session, but the below code is for ‘laravel’: -
public function logout(Request $request)
{
$cookie = \Cookie::forget('first_time');
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/')-
>withCookie($cookie);
Also, you can use ‘\Cookie::queue(\Cookie::forget('first_time'));’ to avoid creating the cookie variable and redirecting with it.
Kindly find the link below for more details: -
https://laracasts.com/discuss/channels/laravel/cookie-is-not-deleting-using-cookieforget-after-logout
• Similarly, I would suggest you follow the cookie-based identity authentication method using the ASP .Net Core sample code as below: -
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication
(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapDefaultControllerRoute();
app.Run();
For more information, kindly find the documentation link below which explains the cookie authentication configuration in detail: -
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

Related

Infinite re-direct loop after AAD Authentication when redirect is specified

If I specify a redirect URI in my OpenIdConnectAuthenticationOptions like so
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
Then I get an infinite re-direct loop. This only happens though when i put it on and standalone IIS Server (our test server). If i remove all the Replay url's in AAD and leave it only setup for the test server, and remove the "RedirectUri = redirectUri," from the above my problem goes away.
I have a fiddler log here : https://drive.google.com/file/d/0B5Ap95E_wdyAa0RLLWloZ0dCaGM/view?usp=sharing
It appears that when request from AAD comes back to my app, before the token is grabbed and used, the Middle Ware is just bouncing it right back with a 302. Also what may be important, I have the [Authorize] attribute over the mvc controller that the routing and return uri directs to. If I remove it i do not get this issue.
[UPDATE]
I tried moving the application to my localhost install of IIS rather than using iisexpress so that i could setup as a SubApplication like it is on my iis server. On my localhost it does the same infinite loop. I added in some telemetry custom events on an override of the [Authorize] attribute and have been able to discover that when page is re-directed back to the application after authentication httpContext.user.identity.IsAuthenticated = false. So somehow the OWIN middle ware is not setting this to true?
Thanks for any help!
I was able to find a solution to my problem. Originally i was specifying my reply url's to point to the root of the site. My rout config looks like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Welcome", action = "Index", id = UrlParameter.Optional }
);
If I append "Welcome" to the end of my reply url it works. For some reason if i leave the reply url to the root of the site and have the default route picked up it just goes into an infinite loop.
I found out also that this only applies to a sub application of a site. I tried moving my application to be a standalone site in iis so rather than and I didnt have to add the controller name in the reply url.
Example:
Original reply url:
mysite.mydomain.com/CustomApp
New Reply url:
mysite.mydomain.com/CustomApp/Welcome
Hope someone else can find this useful!
UPDATE
I found out that the root of the problem was still caused from this mvc5 bug: katanaproject.codeplex.com/workitem/197. I thought it had been fixed but it has not, so i will continue to use the well known Kentor Owin Cookie Saver: github.com/Sustainsys/owin-cookie-saver
Solved by using Never option for CookieSecureOption
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = CookieSecureOption.Never
})

Azure web hook error: The 'code' query parameter provided in the HTTP request did not match the expected value

I have created a c# based web hook in an azure function app, based on Adrian Halls excellent book on github.io
The web hook and app is running successfully when tested in portal.
When i call the webhook from my controller i can see i have the correct parameters and uri. But for some reason my function app never enters my method and give me an error saying:
The 'code' query parameter provided in the HTTP request did not match the expected value
My problem is that i do have my code query parameter in the request.
Basically i just want to trigger the webhook when a new todoitem is inserted in my database.
Anybody know what could be the problem?
Code:
Call from api controller to webhook method in backend
// POST tables/TodoItem
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
TodoItem current = await InsertAsync(item);
Webhook.SendAsync<TodoItem>(new Uri(WebhookUri), current);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
Webhook method in backend
public static async Task<HttpStatusCode> SendAsync<T>(Uri uri, T data)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = uri;
var response = await httpClient.PostAsJsonAsync<T>("",data);
return response.StatusCode;
}
Function in azure
#r "Newtonsoft.Json"
using System;
using System.Net;
using Newtonsoft.Json;
public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("Webhook triggered");
string jsonContent = await req.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(jsonContent);
log.Info($"Created New Todo ({data.Text}, {data.Complete})");
return req.CreateResponse(HttpStatusCode.OK);
}
Had the same issue today. Go to the Manage panel of your Azure Function. There you can copy the "default" key which works
Anders,
There are indeed some issues with the key management UI on the portal and those are being addressed (you can track one that was likely impacting you here
A workaround, at the moment, is to make sure you're using the appropriate key by opening the "keys" panel for the function and selecting the "default" function key, using that as the code.
There is also an API you can use to request the keys directly from the runtime, here's an example of invoking that API to retrieve the keys for a given function:
https://<functionappname>.azurewebsites.net/admin/functions/<functionname>/keys?code=<your admin key>
This must be a bug in azure functions app.
I created a couple of web hook functions more to see if i could hit one of those.
No success same error as before.
But then i went back to my old function and suddenly it worked. I don't know why one of my new web hooks i tested on is a copy of the old one and it is still not working. Maybe an azure functions expert know more about this.

Azure Function App: Authentication Breaks Development Portal

I've added Azure Active Directory Authentication to my function app, but as soon as I set "Action to take when request is not authenticated" to "Login with Azure Active Directory", the development interface for the function app yields this message:
Error:
We are unable to reach your function app. Your app could be having a temporary issue or may be failing to start. You can check logs or try again in a couple of minutes.
Session Id: 23a5880ec94743f5a9d3ac705515b294
Timestamp: 2016-11-16T08:36:54.242Z
Presumably adding the authentication requirement breaks access to the function app in some fashion... though I am able to make changes in the code editor, and they do take effect, I no longer see updates in the log panel: no compilation output messages, for example.
Does anyone know a work-around for this?
So far, I've tried just leaving the auth option to "Allow anonymous requests (no action)" and using this following code:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var user = "Anonymous";
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (claimsPrincipal != null && claimsPrincipal.Identity.IsAuthenticated)
{
user = claimsPrincipal.Identity.Name;
log.Info($"Hello {user}");
}
return req.CreateResponse(HttpStatusCode.OK, "Hello " + user);
}
However, this (rightly) doesn't redirect to the authentication provider... I would prefer to have the app take care of all that gunge for me, but if doing so means I can't see compilation messages / log messages, it makes it pretty hard to see what's going on.
Nathan,
Unfortunately, this is a limitation at the moment and we're tracking it here: https://github.com/projectkudu/AzureFunctionsPortal/issues/794
Your approach, to allow anonymous and validate in the function is what we recommend at the moment. To extend your workaround, you can add the following code to initiate a login redirect when you detect an anonymous user (the code below assumes you are using AAD).
else
{
log.Info("Received an anonymous request! Redirecting...");
var res = req.CreateResponse(HttpStatusCode.Redirect);
res.Headers.Location = new Uri(req.RequestUri, $"/.auth/login/aad?post_login_redirect_uri={req.RequestUri.AbsolutePath}&token_mode=session");
return res;
}
We understand that isn't ideal and appreciate your patience while we work to improve this.
Thanks!

Sharepoint anonymous access to layouts folder in anonymous web application

I have a Sharepoint Foundation server 2013 with a Web Application deployed, a root Site Collection and another Site Collection in this Web Application. The Web Application is configured for Anonymous Access, the second Site Collection requires Sharepoint authentication (MS TMG).
I have Application Pages that are deployed to the server (scope = web), these Application Pages are used within the second Site Collection by users and so require authentication, which works as desired. Those Application Pages must also be accessible anonymously, they are of course in the _layouts folder and so are included in the root Site Collections _layout path, this part does not work.
I can access anonymously the root server address https://myserver.mycompany.co.uk/
(maps to https://myserver.mycompany.co.uk/_layouts/15/start.aspx#/SitePages/Home.aspx which is turn maps to https://myserver.mycompany.co.uk/SitePages/Home.aspx). I cannot however get anonymous access to https://myserver.mycompany.co.uk/_layouts/15/mysite.ApplicationPages/MyPage.aspx?QueryString=etc
It requires authentication and of course works when I provide authentication.
Suggestions? More info required?
// This
public partial class DoWithComment : UnsecuredLayoutsPageBase
{
// And this was required as well
protected override bool AllowAnonymousAccess
{
get
{
return true;
}
}
}
If your app pages need to be accessible via anonymous access, your pages should inherit from Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase instead of LayoutsPageBase
See: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.unsecuredlayoutspagebase.aspx
Apart from Colin's answer there is indeed a case when the above does not work (SharePoint 2013 with SP 1).
SharePoint is accessed via Windows Authentication.
User utilizes Chrome (my version is 35) to access page.
User has been logged off from different browser or user's domain login is locked.
User tries to access the anonymous page.
User gets the login popup from Chrome.
My only workaround was to create a HTTP module to remove all the cookies including WSS_KeepSessionAuthenticated cookie on BeginRequest. Most probably removing the WSS_KeepSessionAuthenticated is only required but I'm pasting original code which removed every cookie as the issue is quite hard to reporduce.
public class SPNoAuthModule : IHttpModule
{
public void Dispose(){ }
public void Init(HttpApplication context)
{
context.BeginRequest+=context_BeginRequest;
}
private void context_BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var context = app.Context;
if (context.Request.FilePath.ToUpper().EndsWith("YOURPAGEADDRESS"))
{
var cookieNames = context.Request.Cookies.AllKeys;
foreach (var cookieName in cookieNames)
{
context.Request.Cookies.Remove(cookieName);
}
}
}
}
And of course register it in proper Web.config in c:\inetpub\wwwroot\wss\VirtualDirectories\YOURAPPNAME:
<modules>
<add name="YOURMODULENAME" type="YOURNAMESPACE.SPNoAuthModule, YOURASSSEMBLYNAME, Version=YOURVERSION, Culture=YOURCULTURE, PublicKeyToken=YOURKEYTOKEN" />
</modules>

WebClient with credentials still not downloading file

I am trying to download files from a website with username/password. You need to pay for a registered account in order to download files - which we have done. I am attempting to pass in the username/password and download a file as follows:
if (docUrl != null)
{
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
this.WebClientInstance.Credentials = new NetworkCredential(username, password);
fileData = this.WebClientInstance.DownloadData(docUrl);
this.WebClientInstance.Dispose();
isDataDownloaded = true;
}
WebClientInstance is a System.Net.WebClient. I debugged and verified that it is hitting the line to set credentials. Instead of downloading the PDF, I end up with an HTML page that prompts me to log in to get access to the file. I have verified that the username/password is correct. I use the same credentials to scrape the website with WatiN.
Is there something else that I'm supposed to be doing here?
UPDATE
Okay, I've done some sniffing around and found some useful info on this issue. I still haven't gotten it to work, but I think I'm closer. First, you need to create a cookie aware WebClient that extends the WebClient class, as follows:
public class CookiesAwareWebClient : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public CookiesAwareWebClient()
{
this.CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest(Uri address)
{
var webRequest = base.GetWebRequest(address);
if (webRequest is HttpWebRequest)
(webRequest as HttpWebRequest).CookieContainer = this.CookieContainer;
return webRequest;
}
}
Next is to use the WebClient.UploadValues() method to upload the login info to the target website. The full process of authenticating and downloading the target resource is as follows:
using (var webClient = new CookiesAwareWebClient())
{
var postData = new NameValueCollection()
{
{ "userId", username },
{ "password", password }
};
webClient.UploadValues(docUrl, postData);
fileData = webClient.DownloadData(docUrl);
}
I was wrong about the site using forms auth. It is a JSP website and uses a JSESSIONID. I have verified that I am getting a cookie back with what appears to be a valid 32-byte JSESSIONID value.
However, when I call WebClient.DownloadData() it is still only returning the redirected login page. I've tried to fix this by setting the AllowAutoRedirect property on the HttpWebRequest to false, but then it returns 0 bytes.
Is there something else that I need to do so it won't redirect and will take me to the resource once I have authenticated?
(Answered in a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Solved. So the problem was between my ears. I was passing in the URL for the secure resource to the .UploadValues() method, knowing that it would redirect to the login page. However, I really needed to pass in the URL from the login form (where it goes upon submitting) - not the login page itself. Once I did that, it worked correctly. I think I'm going to go find a career in food service now.
LINKS
There were already a few questions posted on SO that addressed this issue. I just didn't know what I was looking for at first so I didn't see those... Anywhere here are a couple good resources that I came across when working on this issue:
how to maintaine cookies in between two Url's in asp.net
Trying to get authentication cookie(s) using HttpWebRequest

Resources