How To Add Custom Parameters to Identity Server LogoutRequest? - asp.net-mvc

I have a legacy ASP.NET MVC (Owin) website using the latest OpenIdConnect library, and .NET Core 3.1 MVC Identity Server app running the latest Identity Server package.
I need to pass a custom parameter to Identity Server at logout (the use case is that I need to display a different "logout reason" message depending on how the logout was initiated on the client). So I'm intercepting the OpenIdRequestType.Logout notification setting a custom parameter via n.ProtocolMessage.SetParameter("some_key", "SomeValue") and I can see that the Parameters dictionary has the new value in it. But when the Logout post to my Identity Server comes in, the LogoutRequest.Parameters collection is empty.
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async n =>
{
switch (n.ProtocolMessage.RequestType)
{
case OpenIdConnectRequestType.Logout:
n.ProtocolMessage.SetParameter("some_key", "SomeValue");
break;
...
On the Identity Server side, I'm calling
var logout = await _interaction.GetLogoutContextAsync(logoutId);
and finding that logout.Parameters is empty.
I found this SO question that suggests that the oidc-client javascript library can handle adding additional parameters that will show up in the LogoutRequest object so I'm guessing it's possible and I'm just missing something simple. Any ideas would be appreciated.

Related

Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity

Does anyone have a working sample for Sustainsys Saml2 library for ASP.NET Core WebAPI only project (no Mvc) and what's more important without ASP Identity? The sample provided on github strongly relies on MVC and SignInManager which I do not need nor want to use.
I added Saml2 authentication and at first it worked fine with my IdP (I also checked the StubIdP provided by Sustainsys) for first few steps so:
IdP metadata get properly loaded
My API properly redirects to sign-in page
Sign-in page redirects to /Saml2/Acs page, and I see in the logs that it parses the result successfully
However I don't know how to move forward from there and extract user login and additional claims (my IdP provided also an e-mail, and it is included in SAML response which I confirmed in the logs).
Following some samples found on the web and modyfing a little bit the MVC Sample from GitHub I did the following:
In Startup.cs:
...
.AddSaml2(Saml2Defaults.Scheme,
options =>
{
options.SPOptions.EntityId = new EntityId("...");
options.SPOptions.ServiceCertificates.Add(...));
options.SPOptions.Logger = new SerilogSaml2Adapter();
options.SPOptions.ReturnUrl = new Uri(Culture.Invariant($"https://localhost:44364/Account/Callback?returnUrl=%2F"));
var idp =
new IdentityProvider(new EntityId("..."), options.SPOptions)
{
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = true, // At first /Saml2/Acs page throwed an exception that response was unsolicited so I set it to true
MetadataLocation = "...",
SingleSignOnServiceUrl = new Uri("...") // I need to set it explicitly because my IdP returns different url in the metadata
};
options.IdentityProviders.Add(idp);
});
In AccountContoller.cs (I tried to follow a somewhat similar situation described at how to implement google login in .net core without an entityframework provider):
[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly ILog _log;
public AccountController(ILog log)
{
_log = log;
}
[HttpGet("Login")]
[AllowAnonymous]
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
// It looks like this parameter is ignored, so I set ReturnUrl in Startup.cs
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
[HttpGet("Callback")]
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Constants.Auth.Schema.External);
_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
// HttpContext.User does not contain any data either
// code below is not executed
var claimsIdentity = new ClaimsIdentity(Constants.Auth.Schema.Application);
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
_log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync(Constants.Auth.Schema.Application, new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
TLDR: Configuration for SAML in my ASP.NET Core WebApi project looks fine, and I get success response with proper claims which I checked in the logs. I do not know how to extract this data (either return url is wrong or my callback method should work differently). Also, it is puzzling why successfuly redirect from SSO Sign-In page is treated as "unsolicited", maybe this is the problem?
Thanks for any assistance
For anyone who still needs assistance on this issue, I pushed a full working example to github which uses a .Net Core WebAPI for backend and an Angular client using the WebAPI. you can find the example from here:
https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
As it turned out, the various errors I've been getting were due to my solution being hosted inside docker container. This caused a little malfunction in internal aspnet keychain. More details can be found here (docker is mentioned almost at the end of the article):
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x&view=aspnetcore-2.2
Long story short, for the code to be working I had to add only these lines:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/some/volume/outside/docker")); // it needs to be outside container, even better if it's in redis or other common resource
It fixed everything, which includes:
Sign-in action to external cookie
Unsolicited SSO calls
Exceptions with data protection key chain
So it was very difficult to find, since exceptions thrown by the code didn't point out what's going on (and the unsolicited SSO calls made me think that the SSO provider was wrongly configured). It was only when I disassembled the Saml2 package and tried various code pieces one by one I finally encoutered proper exception (about the key chain) which in turned led me to an article about aspnet data protection.
I provide this answer so that maybe it will help someone, and I added docker tag for proper audience.

ASP .NET MVC websocket client to save data in DB

I am struggling to achieve the following:
I have created a Java websocket server which publishes data every 1 sec.
In ASP MVC projest I would like to receive the data and save them in database only so no JS involved here.
I am able to read the websocket data using console application method below :
using WebSocketSharp;
List<string> readoutList = new List<string>();
public void receiveMessage() {
using (var ws = new WebSocket("ws://localhost:4567/socket/"))
{
ws.OnMessage += (sender, e) =>
{
if (e.IsText)
{
readoutList.Add(e.Data.ToString());
Console.WriteLine(e.Data.ToString());
}
};
ws.Connect();
Console.ReadKey(true);
}
}`
How to create a service of this kind within the ASP MVC project? I need some direction here.
MVC is stateless so you have to request back to the server to initiate the request (such as from a form post) but within the MVC controller response, you can kick off the request to the server (as an example using a different technology). The problem is there isn't necessarily a 1 to 1 translation in MVC; initiating the request using client-side JavaSvcript would be the option here. Initiating these types of requests within MVC may cause issues with timeouts too that you have to be aware of.
OR, you can consider a scheduled windows program or a windows service that is installed, which can manage itself and initiate the request every so often. I think this is the better option.

A simple ASP .NET MVC API controller using roles

I wrote a web application using ASP .NET MVC and authorization system by default. I configured IdentityRole and input through external providers. Using the current database I have created my data context. Now I want to write a Xamarin.Android app and connect to my database, I want a simple API. But the feature that you want to access this API was only available to user with a certain role. The API is really very simple and therefore do not want to add to the draft WCF or WebAPI project. How to do it best?
First, you don't need a separate project to use Web Api; you can use both MVC and Web Api in the same project. For one off endpoints for things like in-site AJAX requests, just creating MVC actions that return JSON or XML would be fine, but if you're talking about a true API, even if it's fairly simplistic, I'd say go Web Api.
You'd protect your Web Api actions much the same as you would your MVC actions, using the [Authorize] attribute. If you need to restrict by role, you just pass a role(s) to that. However, the big difference here, especially if you're serving a mobile app, is that you'll need pass the authorization along with the request. That's generally accomplished using the Authorization header along with a bearer token. Basically, you would need to set up an endpoint that signs a user in and returns a token. Then, each subsequent request that needs authorization includes that token in the header.
I want to finish and to fully answer this question and close this topic. I've been searching for how to add the ability for a mobile client to connect to an existing site on ASP.NET MVC. In my search, I came across a great article Justin Hyland on March 2, 2014
In principle, everything in this article is well and clearly written, but I want to make a tiny contribution for clarity.
Under Setup WebAPIConfig stated that the need
added in the following code to the WebApiConfig Register method
But if we consider the case ASP.NET MVC we don't have such file. It's all very simple, you just need such a file to create the folder App_Start. The contents of the file can be left exactly as it is in the article.
To get rid of the bugs which will inevitably appear we need to install two nuget package: Microsoft.AspNet.WebApi and Microsoft.AspNet.WebApi.Owin.
Excellent! Now we can turn to the method to obtain the token and then adding the token to the query we can get the needed data closed by the attribute [Authorize].
A small remark. If You need to access a method which is closed for a specific role that to the Authenticate method from the article should add a few lines of code. Immediately after the line:
identity.AddClaim(new Claim(ClaimTypes.Name, user));
add the line:
identity.AddClaim(new Claim(ClaimTypes.Role, role));
where role you can get the following, for example:
var userIdentity = UserManager.FindAsync(user, password).Result;
var role = RoleManager.FindById(userIdentity.Roles.First().RoleId).Name;
User and password you have to send a request.
I also want to give an example of code which will send request and receive response. To not have to look for and immediately start coding.
async Task<string> GetToken(string userName, string password)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>( "user", userName ),
new KeyValuePair<string, string> ( "password", password )
}
);
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(APP_PATH + "/Authenticate", content);
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
async Task<string> GetUserInfo(string token)
{
using (var client = CreateClient(token))
{
var response = await client.GetAsync(APP_PATH + "/ValidateToken");
return await response.Content.ReadAsStringAsync();
}
}
HttpClient CreateClient(string accessToken = "")
{
var client = new HttpClient();
if (!string.IsNullOrWhiteSpace(accessToken))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
return client;
}
All have only to call the appropriate methods in the correct order. I hope that is useful to someone.
P.S.
If You create a new project in Visual Studio to get this functionality you just need to tick:

How to add parameters to redirect_uri in WebApi Oauth Owin authentication process?

I'm creating a webapi project with oauth bearer token authenthication and external login providers (google, twitter, facebook etc.). I started with the basic VS 2013 template and got everything to work fine!
However, after a user successfully logs is, the owin infrastructure creates a redirect with the folllowing structure:
http://some.url/#access_token=<the access token>&token_type=bearer&expires_in=1209600
In my server code I want to add an additional parameter to this redirect because in the registration process of my app, a new user needs to first confirm and accept the usage license before he/she is registered as a user. Therefore I want to add the parameter "requiresConfirmation=true" to the redirect. However, I've no clue about how to do this. I tried setting AuthenticationResponseChallenge.Properties.RedirectUri of the AuthenticationManager but this doesn't seem to have any affect.
Any suggestions would be greatly appreciated!
It should be relatively easy with the AuthorizationEndpointResponse notification:
In your custom OAuthAuthorizationServerProvider implementation, simply override AuthorizationEndpointResponse to extract your extra parameter from the ambient response grant, which is created when you call IOwinContext.Authentication.SignIn(properties, identity).
You can then add a custom requiresConfirmation parameter to AdditionalResponseParameters: it will be automatically added to the callback URL (i.e in the fragment when using the implicit flow):
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context) {
var requiresConfirmation = bool.Parse(context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["requiresConfirmation"]);
if (requiresConfirmation) {
context.AdditionalResponseParameters.Add("requiresConfirmation", true);
}
return Task.FromResult<object>(null);
}
In your code calling SignIn, determine whether the user is registered or not and add requiresConfirmation to the AuthenticationProperties container:
var properties = new AuthenticationProperties();
properties.Dictionary.Add("requiresConfirmation", "true"/"false");
context.Authentication.SignIn(properties, identity);
Feel free to ping me if you need more details.

DotNetOpenAuth: Message signature was incorrect

I'm getting a "Message signature was incorrect" exception when trying to authenticate with MyOpenID and Yahoo.
I'm using pretty much the ASP.NET MVC sample code that came with DotNetOpenAuth 3.4.2
public ActionResult Authenticate(string openid)
{
var openIdRelyingParty = new OpenIdRelyingParty();
var authenticationResponse = openIdRelyingParty.GetResponse();
if (authenticationResponse == null)
{
// Stage 2: User submitting identifier
Identifier identifier;
if (Identifier.TryParse(openid, out identifier))
{
var realm = new Realm(Request.Url.Root() + "openid");
var authenticationRequest = openIdRelyingParty.CreateRequest(openid, realm);
authenticationRequest.RedirectToProvider();
}
else
{
return RedirectToAction("login", "home");
}
}
else
{
// Stage 3: OpenID provider sending assertion response
switch (authenticationResponse.Status)
{
case AuthenticationStatus.Authenticated:
{
// TODO
}
case AuthenticationStatus.Failed:
{
throw authenticationResponse.Exception;
}
}
}
return new EmptyResult();
}
Working fine with Google, AOL and others. However, Yahoo and MyOpenID fall into the AuthenticationStatus.Failed case with the following exception:
DotNetOpenAuth.Messaging.Bindings.InvalidSignatureException: Message signature was incorrect.
at DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\ChannelElements\SigningBindingElement.cs:line 139
at DotNetOpenAuth.Messaging.Channel.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\Messaging\Channel.cs:line 992
at DotNetOpenAuth.OpenId.ChannelElements.OpenIdChannel.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\ChannelElements\OpenIdChannel.cs:line 172
at DotNetOpenAuth.Messaging.Channel.ReadFromRequest(HttpRequestInfo httpRequest) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\Messaging\Channel.cs:line 386
at DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.GetResponse(HttpRequestInfo httpRequestInfo) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\RelyingParty\OpenIdRelyingParty.cs:line 540
Appears that others are having the same problem: http://trac.dotnetopenauth.net:8000/ticket/172
Does anyone have a workaround?
Turns out this was an issue with using DotNetOpenAuth in a web farm environment.
When you create your OpenIdRelyingParty make sure you pass null in the constructor.
This puts your web site into OpenID stateless or 'dumb' mode. It's slightly slower for users to log in (if you even notice) but you avoid having to write an IRelyingPartyApplicationStore to allow DotNetOpenAuth to work across your farm;
var openIdRelyingParty = new OpenIdRelyingParty(null);
All this discussion revolves around the following question:
How does Relying Party (RP) make sure the request containing the authentication token is coming from the OP(OpenId Provider ) to which he forwarded the user’s request to?
Following steps explains how it happens
User Request comes to the Replying Party (RP), our website in our case
Application stores a unique signature corresponding to this user in a local signature store (LSS) and then embeds this signature in the Message and forward this Message to OpenId Provider(OP)
User types his credentials and the OP authenticates his Message and then forwards this Message, which has the signature still embedded in it, back to RP
RP compare the signature which is embedded in the Message to the signature which is in LSS and if they match RP authenticate the user
If the LSS vanishes (somehow) before the Message comes back from OP there is nothing for RP to compare the signature with thus it fails to authenticate user and throws error: Message signature was incorrect.
How can LSS Vanish:
ASP.net refreshes the application pool
IIS is restarted
In web farm the Message is served by application hosted on different server
Two solutions to this issue:
RP run’s in dumb mode
a. It does not store and signature locally and thus does not use signature comparison to make sure the Message is coming from the OP to which he forwarded the user to for authentication
b. Instead, once RP received the authentication Message from the OP it send the Message back to OP and ask him to check if he is the one who has authenticate this user and is the originator of the Message. If OP replies Yes I am the originator of this Message and I have created this message then the user is authenticated by RP
Implement your own persistence store that does not vanish, not matter what ASP.net does to the process, much like using SQL to store session state.
We fixed this issue by implementing IRelyingPartyApplicationStore (IOpenIdApplicationStore in newer versions of DotNetOpenAuth) and adding the store class name to the .config
<dotNetOpenAuth>
<openid ...>
<relyingParty>
...
<store type="some.name.space.MyRelyingPartyApplicationStore, some.assembly"/>
</relyingParty>
</openid>
...
</dotNetOpenAuth>
The interface is a composition of two other interfaces with five members all together.
/// <summary>
/// A hybrid of the store interfaces that an OpenID Provider must implement, and
/// an OpenID Relying Party may implement to operate in stateful (smart) mode.
/// </summary>
public interface IOpenIdApplicationStore : ICryptoKeyStore, INonceStore
{
}
We used dumb mode as a quick fix to get up an running, but in the end you'll probably want something like this.

Resources