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.
Related
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.
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.
I have read Dominik's blog post on authentication vs permission modeling using Identity Server (https://leastprivilege.com/2016/12/16/identity-vs-permissions/). Since I am mostly using role based authorization, I am fine using IdentityServer as authentication/authorization endpoint for different client and apis.
My question is how to model Identity and Resource scopes properly? Can client know which roles are allowed for the user on specific resource? If yes, can client know only the user roles for requested resources scopes (and not all roles for all scopes).
As far as I understand the concept, if I request claims about the user via UserInfo endpoint, I am receiving claims which are filtered by claimTypes listed inside requested identity scopes. That means that if client requests roles scope (identity scope with role claimtype), UserInfo endpoint will respond with all role claims including other applications.
Let's take simple MVC example, where the MVC client communicates with API via REST.
MVC client (client) uses Cookie/OIDC Auth middleware and requests: ResponseType = "id_token token", Scope = "openid profile api".
The API (resource) uses IdentityServerBearerToken Auth middleware and demands: RequiredScopes = "api".
The client has UI elements which should be visible based on api role. How can the roles be accessed from client, since the UserInfo endpoint will only return identity scope based claims?
Should client ask the API (resource), which actions are possible? And based on the response show/hide UI elements?
Thank you for any help.
So things are different between IdentityServer3 and IdentityServer4. My example below is based on IdentityServer4.
You need to do a few of things:
define an IdentityResource that grants access to the role claim.
Include that new IdentityResource in the client's AllowedScopes
Have the client request your new IdentityResource as a scope.
So what I did was as follows:
Where I defined my resources:
new IdentityResource
{
Name = "roles",
UserClaims = { JwtClaimType.Role }
}
Defined my client as:
var client = new Client
{
// .. other stuff
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"roles"
}
}
And then used the the following in my javascript client:
new oidc.OidcClient({
// .. other stuff
scope: 'openid profile roles'
});
I was then able to see all the JwtClaimType.Role claims I'd added to the ClaimPrincipal in my javascript client.
You can put the roles in your id_token. Implement GetProfileDataAsync and add your roles when:
context.Caller == Constants.ProfileDataCallers.ClaimsProviderIdentityToken
or if you need them on the api you can also add them when:
context.Caller == onstants.ProfileDataCallers.ClaimsProviderAccessToken
If you make use of RequestedClaimTypes passed in the context you can filter out if a certain identity scope is requested or not.
For access tokens the resource scope matters (the claim types you specify on those scopes are put in the RequestedClaimTypes for all your requested scopes in the case of
context.Caller == Constants.ProfileDataCallers.ClaimsProviderAccessToken)
Both are called above if you ask for id_token token
The user info endpoint can be called by your api in case you use reference access token with openid scope (must be called otherwise you don't have any info) or want more claims than by default in the principal (coming from a jwt access token),
context.Caller == Constants.ProfileDataCallers.UserInfoEndpoint
(here the RequestedClaimTypes are the ones specified on the resource scopes your access token has access to..). You can also choose to include them in the id_token when the resource scope is part of the requested scopes, for example "openid myscope" where myscope is a resource (api for example), by enabling the 'AlwaysIncludeInIdToken' flag.
Fixed it, I needed to add the openid and profile scopes to the client;
AllowedScopes = new List<string>
{
"customAPI.read",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
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.
I have written a simple LightSwitch 2013 application that manages "customers" by adding a customer entity and let LightSwitch handle the attached SQL Server file.
LightSwitch exposes the data with a restful service (ApplicationData.svc) that can be called like this:
https://somesite.azurewebsites.net/ApplicationData.svc/Customers
Now, I want to add another Windows 8 Universal App client application (Store and Phone), aside to the "included" HTML and Silverlight Desktop client. Therefore, I need to call the restful service programatically.
I struggle there with the forms authentication that I have enabled. So I try to log in programatically by code. I do not exactly know what is going on, I tried to analyze the traffic on the wire with fiddler. I see that there is a "LogIn.aspx" page called (GET), then a postback with the credentials filled out by the user is made (POST).
I always get an "401 - unauthorized" response.
My best guess looks like this:
var cookieContainer = new CookieContainer();
var clientHandler = new HttpClientHandler { CookieContainer = cookieContainer };
using (var client = new HttpClient(clientHandler))
{
client.BaseAddress = new Uri("https://somesite.azurewebsites.net");
// Get the login page
var loginGet = client.GetAsync("/LogIn.aspx").Result;
loginGet.EnsureSuccessStatusCode();
// Post-back to login page with credentials
var loginPost = client.PostAsync("/LogIn.aspx", new FormUrlEncodedContent(new Dictionary<string, string> {
{ "LoginUser$Username", "myname" },
{ "LoginUser$Password", "mypw"},
{ "LoginUser$LoginButton", "LOG+IN" },
})).Result;
loginPost.EnsureSuccessStatusCode();
// try to get the customers list via OData
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Getting a "401 - unauthorized" here:
var response = client.GetAsync("ApplicationData.svc/Customers").Result;
response.EnsureSuccessStatusCode();
}
Could someone kick me into the right direction?
PS:
I know that if I wrote an .NET client, I could just use the "Lightswitch.ApplicationData" class to call the restful service seamlessly. This solution is suggested here:
authenticate Lightswitch Odata service that uses forms authentication
But in my case, I have a Windows 8 Universal App, so I cannot reference the "Server" assembly generated by LightSwitch, which is based on the .NET runtime.
So I finaly found it .... I was completely wrong by handling around with cookies ....
A lightswitch OData service with Forms authentication is exposed with Basic Authentication.
https://usernamme:password#somesite.azurewebsites.net/ApplicationData/Customers
Be sure that you are using SSL of course!
See this article: Exposing LightSwitch Application Data