ITfoxtec.Identity.Saml2 - multiple authentication schemas - itfoxtec-identity-saml2

Is there a way to integrate into the same project SAML authentication and form authentication?
I have today only SAML authentication:
services.AddSaml2("/login", true);
If I add another schema after the SAML, the SAML stops working. If I add it before, the from authentication is not triggered.
This is a code of the form authentication:
services.AddAuthentication("Form")
.AddScheme<FormAuthenticationOptions, FormAuthenticationHandler>("Form", null)
.AddCookie(options => {
options.LoginPath = "....";
options.LogoutPath = "...";
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
Please, advise.

I checked it and cause it to work only as follows:
// Add SAML2 schema
services.AddAuthentication(Saml2Constants.AuthenticationScheme)
.AddCookie(Saml2Constants.AuthenticationScheme, o => {
o.LoginPath = new PathString("loginPath");
o.SlidingExpiration = true;
}
);
services.AddAuthentication("TMP")
.AddPolicyScheme("TMP", "TMP Authorization", options => {
options.ForwardDefaultSelector = context => {
if (context.Request.Headers["Form"].Any() || context.Request.Cookies.ContainsKey("Form")) {
return FormAuthenticationOptions.Schema;
}
return Saml2Constants.AuthenticationScheme;
};
})
.AddScheme<FormAuthenticationOptions, FormAuthenticationHandler>("Form", null)
.AddCookie(options => {
options.LoginPath = LoginPath ;
options.LogoutPath = LogoutPath ;
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
The reason for it that itfoxtec adds its schema as default. So I added my schema policy and make the decision as to what schema to go by adding an HTTP header and cookie.
Not so elegant, but works.
I think it will be nice you'll enable only add your library by adding it like this
.AddScheme<SamlAuthenticationOptions, SamlAuthenticationHandler>(FormAuthenticationOptions.Schema, null)
and move the authentication logic to SamlAuthenticationHandler.

You cannot use the services.AddSaml2 in this case because the method do not return the AuthenticationBuilder.
https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2/blob/master/src/ITfoxtec.Identity.Saml2.MvcCore/Configuration/Saml2ServiceCollectionExtensions.cs#L15
Instead, you have to use the code from the method in combination with the new authentication schema(s).
Maybe it would be something like this, but I have not tried it:
services.AddAuthentication(Saml2Constants.AuthenticationScheme)
.AddCookie(Saml2Constants.AuthenticationScheme, o =>
{
o.LoginPath = new PathString(loginPath);
o.SlidingExpiration = slidingExpiration;
if(!string.IsNullOrEmpty(accessDeniedPath))
{
o.AccessDeniedPath = new PathString(accessDeniedPath);
}
})
.AddScheme<FormAuthenticationOptions, FormAuthenticationHandler>("Form", null);

Related

Correct Flow for Google OAuth2 with PKCE through Client App to SAAS API Server

So we are working on a client application in Windows WPF. We want to include Google as a login option and intend to go straight to the current most secure method. At the moment we have spawned a web browser with the following methods to obtain a Authorization Code
private async void HandleGoogleLogin() {
State.Token = null;
var scopes = new string[] { "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "openid" };
var request = GoogleOAuthRequest.BuildLoopbackRequest(scopes);
var listener = new HttpListener();
listener.Prefixes.Add(request.RedirectUri);
listener.Start();
// note: add a reference to System.Windows.Presentation and a 'using System.Windows.Threading' for this to compile
await Dispatcher.Invoke(async () => {
googleLoginBrowser.Address = request.AuthorizationRequestUri;
});
// here, we'll wait for redirection from our hosted webbrowser
var context = await listener.GetContextAsync();
// browser has navigated to our small http servern answer anything here
string html = string.Format("<html><body></body></html>");
var buffer = Encoding.UTF8.GetBytes(html);
context.Response.ContentLength64 = buffer.Length;
var stream = context.Response.OutputStream;
var responseTask = stream.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
stream.Close();
listener.Stop();
});
string error = context.Request.QueryString["error"];
if (error != null)
return;
string state = context.Request.QueryString["state"];
if (state != request.State)
return;
string code = context.Request.QueryString["code"];
await APIController.GoogleLogin(request, code, (success, resultObject) => {
if (!success) {
//Handle all request errors (username already exists, email already exists, etc)
} else {
((App)Application.Current).UserSettings.Email = resultObject["email"].ToString();
((App)Application.Current).SaveSettings();
}
attemptingLogin = false;
});
}
and
public static GoogleOAuthRequest BuildLoopbackRequest(params string[] scopes) {
var request = new GoogleOAuthRequest {
CodeVerifier = RandomDataBase64Url(32),
Scopes = scopes
};
string codeChallenge = Base64UrlEncodeNoPadding(Sha256(request.CodeVerifier));
const string codeChallengeMethod = "S256";
string scope = BuildScopes(scopes);
request.RedirectUri = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
request.State = RandomDataBase64Url(32);
request.AuthorizationRequestUri = string.Format("{0}?response_type=code&scope=openid%20profile{6}&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
AuthorizationEndpoint,
Uri.EscapeDataString(request.RedirectUri),
ClientId,
request.State,
codeChallenge,
codeChallengeMethod,
scope);
return request;
}
To my understanding, from this point the client app has completed the required portion to have the user login to their google account and approve any additional privileges.
Our API/App server is in GoLang.
APIController.GoogleLogin
from above sends the CodeVerifier and AuthorizationCode to the GoLang application server to then finish off the OAuth2 Flow.
Is this the correct flow given our client-server setup?
If so, what is the best practice for the Go Server to retrieve a Access Token/Refresh Token and get user information? Should the client app be performing a looping check-in to the app server as the app server will not immediately have the required information to login?
Thanks for the help!

Identityserver4 Use Two Same External Provider,The Second External Provider Login Always failed

I use two discord provider with different clientid for login.The code below:
services.AddAuthentication()
.AddDiscord("ADiscord", u =>
{
u.ClientId = "74627xxx8536";
u.ClientSecret = "B-FLxxxxjp3JOKwr27";
u.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
u.Scope.Add("guilds.join");
u.SaveTokens = true;
u.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
u.CorrelationCookie.IsEssential = true;
})
.AddDiscord("BDiscord", u =>
{
u.ClientId = "71475xxx1925";
u.ClientSecret = "45-xxxA4okXx1I";
u.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
u.Scope.Add("guilds.join");
u.SaveTokens = true;
u.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
u.CorrelationCookie.IsEssential = true;
});
When i login with scheme "ADiscord" everything is ok.But use scheme "BDiscord" it throw an error "Error from RemoteAuthentication: The oauth state was missing or invalid".
What's even more strange is that if change the order let "BDiscord" on the top then login with scheme "BDiscord" everything is ok,"ADiscord" will throw the error.
Disord provider code is on https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/tree/dev/src/AspNet.Security.OAuth.Discord
You need to set the CallbackPath, set it to sth like /signin-discord and register /signin-discord with the IdSvr4. This endpoint is used to complete the OAuth handshake before redirecting to an application endpoint.

IdentityServer4 Discovery Client Error - Issuer Name Does Not Match Authority

I am getting the 'Issuer name does not match authority' error because I have an ssl-terminating load balancer in front of my is4 service (i.e. issuer is https://myurl and authority is http://myurl).
What should I do in this situation? The dns names are identical, it is the s in https which is causing the validation failure!
It is possible for your Issuer and Authority to be different, but it requires changes to configuration of the server and the discovery request.
On your Identity Server's startup method, you can set the issuer:
var identityServerBuilder = services.AddIdentityServer(options =>
{
if (Environment.IsDevelopment())
{
options.IssuerUri = $"http://myurl:5000";
}
else
{
options.IssuerUri = $"https://myurl";
}
})
And then in your discovery document request:
DiscoveryDocumentRequest discoveryDocument = null;
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == EnvironmentName.Development)
{
discoveryDocument = new DiscoveryDocumentRequest()
{
Address = "http://myurl:5000",
Policy = {
RequireHttps = false,
Authority = "http://myurl:5000",
ValidateEndpoints = false
},
};
}
else
{
discoveryDocument = new DiscoveryDocumentRequest()
{
Address = "http://myurl:5000",
Policy = {
RequireHttps = false,
Authority = "https://myurl",
ValidateEndpoints = false
},
};
}
var disco = await httpClient.GetDiscoveryDocumentAsync(discoveryDocument);
Your issuer url is https however authority url is http. Both urls should be exactly same.
Else you can try setting ValidateIssuerName property to false. This property decides if issuer name has to be identical to authority or not. By default it is true -
var discoRequest = new DiscoveryDocumentRequest
{
Address = "authority",
Policy = new DiscoveryPolicy
{
ValidateIssuerName = false,
},
};
answer from mackie1001 on identityserver4 gitter
your load balancer should forward on the original protocol (X-Forwarded-Proto) and you can use that to set the current request scheme to match the incoming request
you'd just need to create a middleware function to do it
Have a read of this: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.0
for reference this is the code i added to startup:-
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
});
many thanks mackie1001!
if using nginx as the load balancer you will probably need this in service configuration...
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
and then add this middleware before the identity server middleware
app.UseForwardedHeaders();

AspNet.Core Identity Authentification from WPF client

I've implemented asp.net core Identity authentifaiction and it's working fine with my web application. In the startup.cs file, I have the following:
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
And in the Login.chtml.cs, I've the the login method:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
//...
}
else
{
//....
}
}
return Page();
}
Now I'm putting in place a WPF client in which I want to authenticate my users using the AspNetCore.Identity login procedure. Any suggestion about how to proceed will be highly appreciated.
Finally, I decided to go with IdentityServer4 in order to have a centralized login and workflow for the WPF client and other clients that I may need later.

How to retrieve Azure Key Vault in React JS

I have created some setting in Azure and I need fetch some secret keys from there in react js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
var KEY_VAULT_URI = "https://mydomain.com.vault.azure.net/";
msRestAzure.loginWithAppServiceMSI({resource: 'https://vault.azure.net', msiEndpoint: 'https://vault.azure.net', msiSecret: '69418689F1E342DD946CB82994CDA3CB', msiApiVersion: '' }).then((credentials) => {
const keyVaultClient = new KeyVault.KeyVaultClient(credentials);
var data = keyVaultClient.getSecret(KEY_VAULT_URI, 'My_Secret_Key');
console.log(data);
});
I'm getting some issue net::ERR_NAME_NOT_RESOLVED, I think I'm missing something. Could anyone please suggest that how to retrieve that secret keys from Azure in React Js
Using the loginWithAppServiceMSI() method from ms-rest-azure will autodetect if you're on a WebApp and get the token from the MSI endpoint. So you must host your code on Azure webapp. Refer to this article for more details.
function getKeyVaultCredentials(){
return msRestAzure.loginWithAppServiceMSI({resource: 'https://vault.azure.net'});
}
function getKeyVaultSecret(credentials) {
let keyVaultClient = new KeyVault.KeyVaultClient(credentials);
return keyVaultClient.getSecret(KEY_VAULT_URI, 'secret', "");
}
getKeyVaultCredentials().then(
getKeyVaultSecret
).then(function (secret){
console.log(`Your secret value is: ${secret.value}.`);
}).catch(function (err) {
throw (err);
});
If you don't have to use Managed Service Identity (MSI), you can use msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain) to get the credentials.
function getKeyVaultCredentials(){
return msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain);
}

Resources