Best way to set up SSI with SAML and Azure AD - saml

Based on this article, I am under the impression you can use the SAML standard to authenticate users on your web application using Azure AD:
https://learn.microsoft.com/en-us/azure/active-directory/develop/single-sign-on-saml-protocol
However it is not entire clear on all of the small steps required to do this. So I had a go and did the following to figure out for myself the first step which is to get the URL that the Service Provider needs to redirect the browser to authenticate using the IdP (i.e. Azure AD):
I set up a separate AD in Azure, on the free tier.
Within that AD I created an Application.
There was no option in the Application to set up SSI
I then use the following code to generate a URL for the SAML browser request:
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace AzureSAMLExperiment
{
class Program
{
// Call to https://login.microsoftonline.com/common/FederationMetadata/2007-06/FederationMetadata.xml returns the SingleSignOnService element below:
public const string SingleSignOnServiceUrl = "https://login.microsoftonline.com/common/saml2";
public const string SingleSignOnQueryString = "?SAMLRequest={0}";
static void Main(string[] args)
{
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/single-sign-on-saml-protocol
var SAMLRequestXML = $#"<samlp:AuthnRequest
xmlns=""urn:oasis:names:tc:SAML:2.0:assertion""
ID=""id6c1c178c166d486687be4aaf5e482730""
Version=""2.0"" IssueInstant=""{DateTime.UtcNow.ToString("o")}""
xmlns:samlp=""urn:oasis:names:tc:SAML:2.0:protocol"">
<Issuer xmlns=""urn:oasis:names:tc:SAML:2.0:assertion"">ISSUER</Issuer>
</samlp:AuthnRequest>";
var url = $"{SingleSignOnServiceUrl}?SAMLRequest={DeflateEncode(SAMLRequestXML)}";
}
private static string DeflateEncode(string val)
{
var memoryStream = new MemoryStream();
using (var writer = new StreamWriter(new DeflateStream(memoryStream, CompressionMode.Compress, true), new UTF8Encoding(false)))
{
writer.Write(val);
writer.Close();
return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length, Base64FormattingOptions.None);
}
}
}
}
And put the resultant URL in the browser.
I tried a few different values for the ISSUER including
http://localhost
Our company domain name
https://sts.windows.net/{tennant guid}
{tennant guid}
{application guid}
But none of that worked.
As you can see I am using the common URL not the tenant-specific. I am not sure which one is best to use.
In every case I got the following response when visiting the url:
So any directions on what I have done wrong?
Do I need a premium level AD or is free OK?
Should I use a tenant or common end point?
Is there something wrong with the XML or the encoding I have used?

Azure AD provides the active directory service for free. To connect to the active directory via SAML protocol, you need to switch to a paid plan. Once you are on a paid plan and configured the SAML setup, Azure will be acting like a SAML IdP (identity provider). At this point, I would recommend testing the Authentication flow using an external IAM service configured as SAML SP (Service Provider) instead of crafting a solution on your own. E.g., you may try Auth0 as SAML SP for this purpose.
Architecture would be something like this with the above setup;
Your App <= OAuth => Auth0 <= SAML => Azure AD
If you don't want to pay Azure for the SAML support, you could federate users to Azure AD with the WsFed protocol. This is also supported with Auth0.
Your App <= OAuth => Auth0 <= WsFed => Azure AD
This link might be useful with some links for .Net if you need to support SAML protocol within your App and also provides some more links to have a broader view of possibilities.
Disclaimer: I work for Auth0.

Yes you need Azure AD Premium.
Here's an example using a custom SAML connection.
Then Azure AD / Enterprise applications / SSO.

Related

How to enable 2FA using email using keycloak-admin-client in spring boot

My requirement is enable 2FA using email in Keycloak.
When enabled, if user tries to login through email & password ,after user is successfully authenticated ,time based token will be sent to email .
User will do this action from custom UI i.e in our product we have UI to enable/disable 2FA for user.
We are using Keycloak & we want to achieve this using Keycloak API.
I am using keycloak-admin-client to interact with Keycloak API but I did not find sufficient resources to achieve this using keycloak-admin-client.
I am looking a way using keycloak-admin-client how to enable 2FA for user.
Any help will be highly appreciated.
Thank You
You should add custom REST endpoints to Keycloak to be able to enable 2FA from your custom UI. We have done this before. It's not that much complicated, but it requires you to have a look at Keycloak source to see what it's doing when OTP gets activated. Some important classes to check/use are TotpBean, OTPCredentialModel and OTPPolicy.
In order to enable the 2FA, we needed to show the QR code image in our custom UI. So we added an endpoint to Keycloak that instantiates an instance of TotpBean. It's the one that gives you access to the QR code image and the secret value that are required to generate the equivalent string representation of the image so that it could be scanned/entered in the 2FA app (e.g. Google Authenticator). Here is an example of how such an endpoint would look like:
#GET
#Produces({MediaType.APPLICATION_JSON})
#Path("/o2p-enable-config/{email}")
#NoCache
public Response fetchOtpEnableConfig(#Email #PathParam("email") String email) {
UserModel user = session.users().getUserByEmail(email, realm);
TotpBean totp = new TotpBean(session, realm, user, session.getContext().getUri().getRequestUriBuilder());
return Response
.ok(new YouOTPResponseClass("data:image/png;base64, " + totp.getTotpSecretQrCode(), totp.getTotpSecret(), totp.getTotpSecretEncoded()))
.build();
}
Then on your own backend, you call this endpoint and send the user's email to it and receive the image and the secret value. You can just display the image as is in your UI and keep the secret value on your backend (e.g. in user's session). When user scans the image using the app and enters the totp value provided by the app in your custom UI, you send the totp value and the secret to another endpoint that you should add to the Keycloak. This second endpoint is the one that does that verification of the value and enables 2FA.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Path("/enable-2fa/{email}")
#NoCache
public Response enable2Fa(#Email #PathParam("email") String email, OtpDetails optDetails) {
OTPPolicy policy = realm.getOTPPolicy();
String totp = optDetails.getTotp();
UserModel user = session.users().getUserByEmail(email, realm);
OTPCredentialModel credential = OTPCredentialModel.createFromPolicy(realm, optDetails.getSecret(), optDetails.getUserLabel());
if (CredentialValidation.validOTP(totp, credential, policy.getLookAheadWindow())) {
CredentialHelper.createOTPCredential(session, realm, user, totp, credential);
return Response.noContent().status(204).build();
} else {
return Response.status(BAD_REQUEST).build();
}
}
Keycloak supports multiple 2FA for each user. That's why it also has a property named label that allows user to name them so that it would be displayed in the 2FA login scenario with given name. You can also allow user to enter the label value in your custom UI and pass it to the second endpoint (or just pass an empty value to Keycloak if you're not going to allow your users to setup multiple 2FA).
I know it seems complicated, but it's actually not that much. The Keycloak domain model is well designed and when you get familiar with it, you can easily find what you need to do and wrap it in custom APIs. But always ensure that exposing a functionality would not compromise the overall security model of the system.
Take a look at keycloak two factor email authenticator provider
https://github.com/mesutpiskin/keycloak-2fa-email-authenticator
I agree that is necessary to write a custom provider for this use case.
Take a look at https://www.n-k.de/2020/12/keycloak-2fa-sms-authentication.html and https://www.youtube.com/watch?v=GQi19817fFk for a look at how to implement that.
That is an example via SMS, but via e-mail would be very similar, changing just the way of sending the code to the user.

Azure AD Redirect URL Using Application Gateway and ASE

We have an ASP Core 2.0 App working nicely with Azure AD on a public network. Our Test environment is running in an Azure ASE. The user starts with a public address that passes through the Azure Application Gateway and gets routed to 1 of 2 App servers in the ASE. The application is registered in Azure AD with response URL's that specify the public address.
The problem is when the user redirects to login, the request address presented to Azure AD is an internal address from one of the 2 servers. Then the response URL's don't match and we get an error at login.
The question is how to present the public address to Azure AD so the response URL's match and the token is posted back to the app using the same? The app gateway, I'm told, is configured to populate x-forwarded-for header which has the original address. I don't see where in the web application this can be controlled.
startup.cs
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options =>
{
Configuration.Bind("AzureAd", options);
AzureAdOptions.Settings = options;
})
.AddCookie();
AccountController.cs
public IActionResult SignIn()
{
var redirectUrl = _azureAdOptions.WebBaseUrl;
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
I would think this is a common configuration - passing public to private servers with SSO integrated.
[Edit]
Based on the link provided in the comments, which was very helpful, we tried several things including explicitly setting UseforwardedHeaders in startup.cs even though this is supposed to be enabled by default. Nothing we did changed the URL bolded in the URL below.
https://login.microsoftonline.com/2ff13e34-f33f-498b-982a-7cb336e12bc6/oauth2/authorize?client_id=998c48ae-bbcf-4724-b6f4-6517e41d180a&redirect_uri=**http%3A%2F%2Flocalhost%3A2345%2Fsignin-oidc**&resource=https%3A%2F%2Fgraph.windows.net&response_type=id_token%20code&scope=openid%20profile&response_mode=form_post......
However, and maybe this is a clue, if we comment out the [Authorize] on the home controller and login after the user clicks a button to login, it works. Why?
Note: IDs/GUIDs above have been scrambled to protect the innocent
I came across this post explaining how Application Gateway did not implement the standard x-forwarded-host headers. I'm hoping this gets fixed so the code below would not be required. The solution that worked in our configuration was to force both the public domain and scheme (HTTPS) on every request because the app gateway wasn't (and apparently couldn't be) configured to pass SSL to the backend servers.
Added to startup.cs
app.Use((ctx, next) =>
{
ctx.Request.Host = new HostString(options.Value.CustomDomain;
ctx.Request.Scheme = "https";
return next();
});
Now when the application redirects for any secure resource -- [Authorize] controller methods, or code that explicitly calls Challenge(x, y, z) the public domain on HTTPS is used as the origin host and scheme. Thanks to #juunas for pointing in the right direction.

Manage AzureSearch via (Rest) API

I’m trying to create a tool which should scale the „Replicas“ and „Partitions“ of an Azure Search component.
For that, I read the following article from Microsoft:
https://learn.microsoft.com/en-us/rest/api/
Right now, I am having trouble authenticating against azure to get an AuthToken.
Is there a way to do it easier? Alternatively, do you guys have a sample in how to do it?
Here is a sample of my code:
var clientId = "2aaced54873e4a94b6d5518bc815dcb1";
var redirectUri = new Uri("https://thissucks.search.windows.net");
var resource = "resource"; // What exactly should the value be?
var authContext =
new AuthenticationContext(
"https://login.windows.net/ba1cb781739c4cdea71c619ccba914e0/oauth2/authorize", new TokenCache());
var result = authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
var result2 = result.Result;
After invoking this, I get an Azure Login screen. After login with valid credentials, I get the following Exception:
System.AggregateException: 'One or more errors occurred.'
InnerException:
AdalServiceException: AADSTS50001: The application named was not found in the tenant named .
This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.
You might have sent your authentication request to the wrong tenant.
So there're a few issues in your code.
First, please make sure that you have followed the steps described here: https://learn.microsoft.com/en-us/rest/api. Once the application is created successfully, you must note down the client id of that application and use that in your code.
Next, please ensure that ba1cb781739c4cdea71c619ccba914e0 is indeed the tenant id. You could also use Azure AD domain name (something.onmicrosoft.com) instead of this GUID type value. So your URL would be https://login.windows.net/something.onmicrosoft.com/oauth2/authorize
Lastly, there are issues with the values for the following parameters:
var redirectUri = new Uri("https://thissucks.search.windows.net");
var resource = "resource"; // What exactly should the value be?
redirectUri is the URI where the Azure AD will redirect once the user is successfully authenticated. For Web Applications it is usually the URL of your website. Please make sure that it matches with the value you provided when creating an application in Azure AD. When Azure AD redirects the user to this URL, it passes a JWT token in code query string parameter using which you get access/refresh token.
resource is the resource for which you're acquiring the token. Since you want to access Resource Manager API, the value here should be https://management.core.windows.net/.

Identityserver3 - Select Identityprovider a client should use

I now have identityserver3 setup, i have 3 identityproviders configured:
- Local
- Google
- ADFS
I have multiple clients using Oidc-Client-JS (https://github.com/IdentityModel/oidc-client-js).
Now i would like to specify which identityprovider a client should use to login. so lets say:
Client A lets the user choose which provider to use
Client B logs in with local
Client C logs in with google
Client D logs in with ADFS
The situation of Client A is the default behavior and i have that working. My question is how do i set up clients B,C and D?
Check the following function in your start up see what you called your Identityprovider in my case "Google".
public static void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
AuthenticationType = "Google",
In your client set the acr_value for idp to what ever you have set.
let userManagerSettings: Oidc.UserManagerSettings = {
acr_values: "idp:Google",
Now the client will automatically redirect to the correct identityprovider
According to the IdentityServer3 documentation, you need to configure the IdentityProviderRestrictions for each clients. In case of only one identity provider is configured, the IdSrv3 will automatically redirect.

IdentityServer3: How to assign ClientSecret to MVC Client?

I have configured IdentityServer3 with EF and AspNetIdentity. I have 3 MVC client applications. All users and clients are configured in SQL DB. I was able to get it working and users can now log in.
Now I'm trying to add some security around Client & Users and I have few questions related
1> Does MVC client only works with Implicit Flow? I have MVC client and below is it's OWIN startup.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44314/identity",
Scope = "openid",
ClientId = "LocalHostMvcClient",
RedirectUri = "http://localhost:34937/",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
}
}
On IdentityServer, when I change the flow of LocalHostMvcClient to any flow (Other than 'Implicit') then client get error The client application is not known or is not authorized.. So it looks like for MVC client
it only works with Implicit flow.
Is this true?
2> Is ClientSecret not relevant for Implicit flow?
I want to use ClientSecret, but looks like it is not relevant for Implicit flow. Based on documentation ClientSecret is relevant to only flows that require secret. If answer to question 1 is true then does that mean i cannot use ClientSecret with Implicit Flow?
3> If i have multiple clients and users. Can we assign a user to a particular client?
For example, if i have 3 clients, www.client1.com, www.client2.com,www.client3.com and 2 users User1,User2. I want User1 to be able to login only for www.client1.com
Is this possible?
ASP.NET MVC can use any OpenID Connect flow. The error you are receiving is due to the client application requesting something it is not allowed to or otherwise being misconfigured in some way. Enable logging in Identity Server and it'll soon tell you why.
Client Secret is not used in Implicit, as implicit relies on the requesting url, not any sort of explicit authorization. That's why it's useful for client-side languages.
This is authorization logic and should be handled within the client application. For example when they login they would be shown an 'unauthorized' page. Identity Server is for authentication only.