Passing State and Other Attributes at Login Using Microsoft.Identity.Web - asp.net-core

I'm having troubles getting the .NET Core 6.0 authentication libraries working as I did with prior versions of .NET Framework. Specifically, I'm using Microsoft.Identity.Web, but have not figured out how to get the scope and state parameters passed in and out that I want. The signin-oidc keeps overriding these values with the ones I am trying to manage dynamically. If I use the old method, which is just a RedirectResult to a properly formed URL for authentication, signin-oidc overrides the values I send in on the query string. If I use the Challenge class with a list of name value pairs in the Properties collection (like state, consent, scope, etc), signin-oidc still overrides them.
I would like to have something as simple, readable, and operationally efficient as when I could just redirect to a URL with all the query string parameters, and provide a callback URL that had a "code", "error", "state", and other parameters in the method. Right now I'm tied in knots over getting the right values over, and trying to get them back in a generic event override in the app's Program start up code. Some stuff shows up in a generic Properties bag, but not in the logical "State" property of the context argument's ProtocolMessage. What am I missing?
These are for cases where the user has authenticated to the site, and now I am trying to get them to consent and get a token for another Azure AD app.
Here is an example of using a simple redirect:
https://login.windows.net/organizations/oauth2/v2.0/authorize?response_type=code%20id_token&client_id=aaaaaaaa-d4d2-4499-8e2a-b6957678fe80&redirect_uri=https%3A%2F%2Flocalhost%2Ffilemonweb%2Fhome%2FProcessCode&state=my%20state%20stuff&scope=openid%20offline_access%20api%3A%2F%2F1f43d4aa-d4d2-4499-8e2a-b6957678fe80%2F.default&response_mode=form_post&nonce=aee597a2-f050-48d0-9e3c-6c62f089b76c&prompt=consent
Here's an example using Challenge:
return Challenge(new AuthenticationProperties(
new Dictionary<string, string?>() { { "state", "my state stuff" } },
new Dictionary<string, object?>() {
{ "client_id", ServiceConfig.Configuration.FILEMON_WEB_CLIENT_ID },
{ "type", "code%20id_token" },
{ "scope", "openid%20offline_access%20" + Uri.EscapeDataString(ServiceConfig.Configuration.FILEMON_WEB_SCOPE) },
{ "response_mode", "form_post" },
{ "nonce", Guid.NewGuid().ToString() },
{ "prompt", "consent"} })
{ RedirectUri = "filemonweb/home/tests" },
"OpenIdConnect");
With the Edge dev tools network sniffer, I see that the request arrives as expected with the top example (properly formatted URL). When the response is returned, I see it go to the redirect URI I requested, but the app sends a 302 response, and the response header includes a Location value that changes the redirect URI from the one I requested, to signin-oidc. For example, like this:
https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=1f43d4aa-d4d2-4499-8e2a-b6957678fe80&redirect_uri=https://localhost/filemonweb/signin-oidc&response_type=code&scope=openid profile offline_access user.read&code_challenge=ZXiKTCHjJJZwo54YdulG7h45_9e4EOBnSv_kxNOTv2w&code_challenge_method=S256&response_mode=form_post&nonce=637860882099818382.MDA0NGY1MGYtZWI0Ny00ZDU3LWJiMjQtZmE1ODk5ODdkYTEyYjQ0M2EzNjgtNzJjZi00ZjEyLWIxNTgtMGE2Y2JkYzc5YWE1&client_info=1&x-client-brkrver=IDWeb.1.16.0.0&state=CfDJ8MyJsY2aFrtOs5mEt79T88KiVV7RXqWFSO0rcSzX-NZcXlZ52qcIxpYLyz0wuWkqCh5vYPEg5Wj-YRUNMD542mvxJDGiKHz62k1hTctyvJxEtlIcZtbvLu1VOE9lNJdd6dKttBP2oi5nwDVZZC96-4bohWxxzfSk0-co8iet8xWhv8k0V2Iva1eatQ__LHOJofFQSV2IUHHmzokTB3s6reO4iLcGCyANYQWl9tp24IdQMWrwp3ZE4-DCDDQ1xzG5DZSbLAAyN29gOe5aAUwJBhmNIYX4Lm7fdsS9Bq9Xsh65h4E8Pff3U531KlDdY2WZK3gB-fyoML6rpT7DRQBN1Z5ls686pyMxtQRVN-LcQEYXCsmv7WZF3yiSQ-ctIN3X1GOehgTPJrSxpb8LxoT-Z9Bo_lQLEmwOvXw-9qbDzntv&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.12.1.0
When it sends that 302 response, it changes the State parameter, the Scope parameter, etc.
Thanks.

Related

Is it possible to define extra details in the callerContext of a Cognito pre-token lambda call?

In Cognito, I have a pretoken generation hook setup and my lambda receives this
{
"callerContext": {
"clientId": "abcde12345" // some hash,
...
},
"request": {
"userAttributes": {
"email": "foo#bar.com",
"sub": "someUserID",
...
},
...
},
...
}
I would like to be able to determine what claims to assign this user, not by the user attributes, but by the app the user is using to login.
Naturally, I could just use callContext.clientId but that can change when an app client is recreated. Is there a way to use a custom string from the callerContext? e.g
"callerContext": {
"clientId": "abcde12345" // some hash,
"appName": "FoobarGame"
}
Is it possible to define this custom string in the Cognito App client instead of relying on the cognito Id?(which can change upon recreation).
I don't think this is doable. At least in the document, they haven't mentioned a way to use a custom string within the callerContext.
As your requirement is: to avoid the code changes, based on the changes in client id, I would suggest to use lambda environment variables.
So we can specify the client Id as an environment variable. Then in the code, retrieve it from there. With that approach, way we don't have to change the code and only the environment variable value.
Would that work for you?

ASP.NET Core : Return Json response on Unauthorized in a filter at the controller/action level

I am not using Identity.
I have this ASP.NET Core configuration enabling two authentication schemes, cookies and basic auth:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "_auth";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/LogOff");
options.AccessDeniedPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
BasicAuthenticationHandler is a custom class inheriting from AuthenticationHandler and overriding HandleAuthenticateAsync to check the request headers for basic authentication challenge, and returns either AuthenticateResult.Fail() or AuthenticateResult.Success() with a ticket and the user claims.
It works fine as is:
Controllers/Actions with the [Authorize] tag will check the cookies and redirect to the login page is not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication")] tag will check the header and reply a 401 Unauthorized HTTP code if not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] tag will allow both methods to access the page, but somehow use the Cookies redirection mechanism when failing both checks.
My goal is to have most of my project to use Cookies (hence why it is set as default), but have some API type of controllers to accept both methods. It should also be possible to tag the Controllers/Actions to return a specific Json body when desired (as opposed to the login redirect or base 401 response), but only for certain controllers.
I've spent the last 2 days reading different similar questions and answers here on StackOverflow, nothing seems to accommodate my need.
Here's a few methods I found:
The options under AddCookie allow you to set certain events, like OnRedirectToAccessDenied and change the response from there. This does not work because it applies to the whole project.
Under my BasicAuthenticationHandler class, the AuthenticationHandler class allow to override HandleChallengeAsync to change the response from there instead of replying 401. Unfortunately, again it applies globally to everywhere you use the scheme, not on a controller/action level. Not sure if it's applied when mixing multiple schemes either.
Many answers point to adding a Middleware to the solution, again, it impacts the whole project.
Many answers point to Policies, but it seems to be to control whether or not an user have access to the resource based on claims, not controlling the response when he do not.
Many answers suggest creating a class inheriting from AuthorizeAttribute, IAuthorizationFilter. Again, this allow to override the OnAuthorization method to decide if the user have the right or not to access the resource, but not to control the response AFTER the normal authentication scheme failed.
I'm thinking either there's a filter type I'm missing, or maybe I need to create a third authentication type that will mix the previous two and control the response from there. Finding a way to add a custom error message in the options would also be nice.
I managed to do it via a IAuthorizationMiddlewareResultHandler. Not my favorite approach because there can be only one per project and it intercepts all calls, but by checking if a specific (empty) attribute is set, I can control the flow:
public class JsonAuthorizationAttribute : Attribute
{
public string Message { get; set; }
}
public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(RequestDelegate requestDelegate, HttpContext httpContext, AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult)
{
// if the authorization was forbidden and the resource had specific attribute, respond as json
if (policyAuthorizationResult.Forbidden)
{
var endpoint = httpContext.GetEndpoint();
var jsonHeader = endpoint?.Metadata?.GetMetadata<JsonAuthorizationAttribute>();
if (jsonHeader != null)
{
var message = "Invalid User Credentials";
if (!string.IsNullOrEmpty(jsonHeader.Message))
message = jsonHeader.Message;
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "application/json";
var jsonResponse = JsonSerializer.Serialize(new
{
error = message
});
await httpContext.Response.WriteAsync(jsonResponse);
return;
}
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
I was typing this on comment... but it's doesn't fit... so here is something we probably need to make clear before choosing a solution:
Authorization process happen at the upper middleware above controller
Yes, AuthorizationMiddleware was registered when we use app.UseAuthorization();, that quite far above controller layer, so it was returned long before the request can reach controller, so, any type of filter cannot be applied here.
Not specify an authentication scheme or policy would easily lead to un-stable behavior.
Imagine, Authentication process return an instance of User that stick with the request, but what would happen if the permission on cookie and basicAuth was difference, like cookie have myclaim, while basicAuth doens't ? Related process on both type of scheme was difference (like challenge on cookie would lead to /Account/Login and basicAuth to /Login ?). And various logic case that we could implement on each page.
I Know, this is not possible, but it would become a mess, not for the author of these code, but for those maintainers to come.
Json response for some specific process on client ?
This might sound detailed at first glance, but it would rather become burden soon, if some more authentication requirement raise after that (like Jwt). Covering each of these case on client would make user experience quite awkward (like, half-authentication and authorization).
And if It's un-avoidable in the project. Might I suggest create a default authentication scheme with ForwardDefaultSelector that would elected which authentication scheme to use for each request. And maintain a stable routing HashSet that would use to detect on which endpoint to set Json Response as wished on some upper level than AuthorizationMiddleware, by using middleware, ofcourse. Then, we narrow down to 2 centralize places to checkout the authorization.
Chaos came when we tried to make one thing to do somethings. At least in this case, I think we would breath easier when coming to debug phase.

Custom API route will respond with Forbidden if JWT token generated with Skoruba (Identity Server 4) is used

We've started implementing a new Web API with ASP.NET Core 2.2 and it has been decided that it should use Identity Server 4 for authentication/authorization duties. Furthermore, its Skoruba implementation has been chosen as it looks like it should fulfill most, if not all our needs in that regard.
We got the identity server and Skoruba up and running, but when it comes to consuming the JWT token in our own API, even assigning just one role to the test user, we'll keep hitting the same brick wall. The following request to the Skoruba API will respond with a 200:
http://localhost:5000/connect/token:
It successfully returns a JSON string with access_token, expires_in and token_type ("Bearer").
After that, a request to the http://localhost:5000/connect/userinfo route of the API with the following header
will also respond with a 200 and return the following JSON string:
{
"sub": "aeccf460-7d0d-41ae-8b52-a051138f5c05",
"role": "Administrator",
"preferred_username": "dev",
"name": "dev"
}
Please take notice that "role": "Administrator" assigned to user dev is something I set up myself using the Skoruba Admin UI, so that JSON is pretty much what I wanted. So for all intended purposes it looks like I have the information I need right now. I just can't consume it. If I try to retrieve the JWT token in our own back end, I am successful (this is obviously just for testing purposes):
[HttpGet("GetAccessToken")]
[AllowAnonymous]
public string GetAccessToken()
{
var accessToken = HttpContext.Request.Headers["Authorization"];
var token = accessToken.First().Remove(0, "Bearer ".Length);
return token;
}
With all that said, onto the actual problem: calls to a route that demands authorization in our API are treated in the same fashion as calls to Skoruba's userinfo action (particularly, the headers):
Inside this same Controller ("Foo"), I implemented a simple Get method, which should only be accessed with the correct role, which I assume is information fetched from HttpContext.Request.Headers["Authorization"] and hoped the framework would use it accordingly:
[HttpGet]
[Authorize(Roles = "Administrator")]
public IActionResult Get()
{
try
{
var response = ConvertListToJsonResponse(GetAll()); //Don't mind me
return Ok(response);
}
//...
}
At this point, my API server responds with the infamous 403 Forbidden status code. Not sure where to go from here and research proved unwieldy so far. Any help is appreciated, I'll provide more code and info if necessary.
EDIT
This is the generated token:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4NTMzNmFmZTY0Yzg2ZWQ3NDU5YzE5YzQ4ZjQzNzI3IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1Njg3NDU5NTgsImV4cCI6MTU2ODc0OTU1OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL3Jlc291cmNlcyIsImNsaWVudF9pZCI6ImF1dGhfdGVzdCIsImNsaWVudF9hY2Nlc3NfcmlnaHRzIjpbImxpc3QiLCJhcHByb3ZlIiwia2VlcCJdLCJzdWIiOiJhZWNjZjQ2MC03ZDBkLTQxYWUtOGI1Mi1hMDUxMTM4ZjVjMDUiLCJhdXRoX3RpbWUiOjE1Njg3NDU5NTgsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sImFtciI6WyJwd2QiXX0.Ihsi6W5ukGcZ4Chkuk5XMaoqTkUR_1hBQlIcdHtMWiBA-EyAIncX5STCng_6ZPgN89Np6Y_hemFFyVtHEdN_vP6i0HuaXgznzrnJ4zq4Iiz9jxpZqpSSE9cXpSG8hPOZe5kGfD2J6_GPxnraGH_1ZF94AhmlspIvqFAAQrQ-0c7-dCduP4ledkQvBKz-rXszGp35W7Gb5nvpcVt4oe67mqETdwtgGengk2eCwHeKdA94EYnj_HErPNTjJhh5k75fDQ0IiOS-xHRK8BQmLhRh_UZwB3H5qZymFJNr_yb-ljFqIeEHptSWLBO1XrKYs1BqB9KwxIROKqmxeNGTnpCUSQ
The resulting payload:
{
"nbf": 1568745958,
"exp": 1568749558,
"iss": "http://localhost:5000",
"aud": "http://localhost:5000/resources",
"client_id": "auth_test",
"client_access_rights": [
"list",
"approve",
"keep"
],
"sub": "aeccf460-7d0d-41ae-8b52-a051138f5c05",
"auth_time": 1568745958,
"idp": "local",
"scope": [
"openid",
"profile",
"roles"
],
"amr": [
"pwd"
]
}
I see the claims I added to the client, but what I really need at the moment is simple Authentication, which I suppose should be provided by a role. Or am I completely off?
This question, and more specifically, this answer helped me to understand what was going on and map Skoruba's UI functionalities to IdentityServer4 inner workings. Credits goes to Ruard van Elburg.

changing meteor restivus PUT to implement upsert

i'm using restivus with meteor and would like to change the PUT schemantic to an upsert.
// config rest endpoints
Restivus.configure({
useAuth: false,
prettyJson: false
});
Restivus.addCollection("sensor", {
excludedEndpoints: ['getAll','deleteAll','delete'],
defaultOptions: {},
});
how does one do this?
Right now, the only way to do this would be to provide a custom PUT endpoint on each collection route:
Restivus.addCollection(Sensors, {
excludedEndpoints: ['getAll','deleteAll','delete'],
endpoints: {
put: function () {
var entityIsUpdated = Sensors.upsert(this.urlParams.id, this.bodyParams);
if (entityIsUpdated) {
var entity = Sensors.findOne(this.urlParams.id);
return {status: "success", data: entity};
}
else {
return {
statusCode: 404,
body: {status: "fail", message: "Sensor not found"}
}
}
}
}
});
The goal with Restivus is to provide the best REST practices by default, and enough flexibility to allow the user to override it with custom behavior where they desire. The proper RESTful behavior of PUT is to completely replace the entity with a given ID. It should never generate a new entity (that's what POST is for). For collections, Restivus will only allow you to define a PUT on a specific entity. In your example, an endpoint is generated for PUT /api/sensors/:id. If you aren't doing the PUT by :id, then you should probably be using POST instead (there's no "right way" to do this in REST, but at least you can POST without requiring an :id).
It sounds like what you want is a way to override the default behavior of the collections endpoints. That is extremely doable, but it would help me if you would make a feature request via the Restivus GitHub Issues so I can better track it. You can literally copy and paste your question from here. I'll make sure I add a way for you to access the collection in the context of any collection endpoints you define.
Last, but certainly not least, I noticed you are using v0.6.0, which needs to be updated to 0.6.1 immediately to fix an existing bug which prevents you from adding existing collections or using any collections created in Restivus anywhere else. That wasn't the intended behavior, and an update has been released. Check out the docs for more on that.

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.
We provide a catalog to customers and give them a URL they can use:
www.ourhost.com/customername
Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as
www.ourhost.com/index.aspx
with index.aspx having access to the customer's ID via its context and it can do its thing.
This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.
Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:
www.ourhost.com/api/{controller}
but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be
www.ourhost.com/customername/api/{controller}
But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.
Is this even possible?
*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.
There are two parts of the answer to your issue that I can see.
Maintaining the User Info across multiple requests
Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.
That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs
protected void Application_PostAuthorizeRequest()
{
// To enable session state in the WebAPI.
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Authorising A Customer in the Request
As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.
First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;
config.Routes.MapHttpRoute(
name: "WithCustomerApi",
routeTemplate: "api/{customername}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route
So you will add an attribute something like so;
[WebApiAuthentication]
public class BaseApiController : ApiController
{
}
Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).
public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.ControllerContext.Request.GetRouteData();
var currentContext = HttpContext.Current;
if (routeData.Route.RouteTemplate.Contains("customername"))
{
try
{
var authenticated = currentContext.Request.IsAuthenticated;
if (!authenticated)
{
var customer = routeData.Values["customername"];
// do something with customer here and then put into session or cache
currentContext.Session.Add("CustomerName", customer);
}
}
catch (Exception exception)
{
var error = exception.Message;
// We dont like the request
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
// No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.
This will echo the customer name back if all works ok.
[WebApiAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var session = HttpContext.Current.Session;
if (session != null)
{
return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
}
return new string[] { "value1", "value2" };
}
}
I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question.
Hope that helps,
Steve