I downloaded the sample IdentityServer and MVC client project from Github.
My goal is to create a portal environment, in which a user can authenticate and than proceed to request protected resources.
So what I did is move the Views from the MVC client to the IdentityServer project, changed the Client.cs to support a client on the same port and added the following in the startup from the IdentityServer:
var oidcOptions = new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
PostLogoutRedirectUri = "http://localhost:5000/",
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
};
My question is are there any reasons, concerning security, I should not implement this and keep it seperated?
There is nothing wrong with this at all from a security perspective.
However I must warn you that you may run into a problem when dealing with cookies and tokens. Running them in separate projects implicitly separates the concerns of the MVC and IDS projects.
something that you might want to do is to fork your request pipeline by using app.Map(). (eg map IDS to "/identity" and the MVC project to "/ui")
Related
I have a standalone blazor WASM app that needs to send requests to an external app. I need to validate the authentication that I receive from the WASM app in the API.
I can see the Bearer token being sent and I checked it up in jwt.io, the data seems to make sense.
The client configuration looks like so :
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common",
"ClientId": "****",
"ValidateAuthority": true
}
I've tried the following TokenValidationParameters without success:
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, $"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration");
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
var validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys,
ValidIssuer = config.Issuer,
ValidateLifetime = false
};
I've loosened up the "Validate", hoping to find what's wrong, but I keep getting "Signature validation failed"
Any idea how I can validate the token?
There're 2 methods to validate the token. The first is writing a custom filter to intercept all the incoming requests and as you know there's a bearer token in the request header, then you can use jwt decode library to decode the token and validate the claims.
But I still recommend following official sample to protect your api via Azure AD directly. And here's a tutorial. You need to expose an API in Azure AD, then configure your app.
By the way, you've had the access token, I think you should have had your exposed api in AAD, so it mostly like that you only need to add authentication in your api project. Then In asp.net core web API project, you need to modify Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
And appsetting.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "37xxxxxxxb2d7",
"TenantId": "21xxxxxxd93",
"Audience": "api://37xxxxxb2d7"
},
Then in the controller, add [Authorize] attribute.
Im currently struggling to connect a ASP.NET Core 2.2 Web API to an existing Azure AD. I based my configuration upon this sample code by the ASP.NET Core team. Cookies were replaced with JWTs.
Unable to retrieve document from metadata adress
Now I face the following error message:
IOException: IDX10804: Unable to retrieve document from: {MetadataAdress}.
- Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+<GetDocumentAsync>d__8.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever
+<GetAsync>d__3.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.ConfigurationManager+<GetConfigurationAsync>d__24.MoveNext()
When I call the URL directly, I receive an instant response with the configuration file. However, the code does not seem to be able to do it. Im not sure what the reason could be.
Azure AD Configuration Syntax
The most likely cause of this issue is a configuration mistake. Maybe I have mistaken a field's syntax or am missing an important value.
Connection Info Fields
The connection info fields are provided like this:
TenantId: {Tenant-GUID}
Authority: https://login.microsoftonline.com/{TenantId}
Resource: https://{resource-endpoint}.{resource-domain}
ClientId: {Client-GUID}
ClientSecret: {ClientSecret}
Service Configuration
The authentication service configuration in the Startup.cs looks like this:
services
.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer()
.AddOpenIdConnect(options => {
options.ClientId = this.ClientId;
options.ClientSecret = this.ClientSecret;
options.Authority = this.Authority;
options.Resource = this.Resource;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SignedOutRedirectUri = "/signed-out";
options.Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
var request = context.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(
request.Scheme, request.Host, request.PathBase, request.Path
);
var credentials = new ClientCredential(this.ClientId, this.ClientSecret);
var authContext = new AuthenticationContext(
this.Authority,
AuthPropertiesTokenCache.ForCodeRedemption(context.Properties)
);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code,
new System.Uri(currentUri),
credentials,
this.Resource
);
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
// Custom
options.MetadataAddress = $"{this.Authority}/federationmetadata/2007-06/federationmetadata.xml";
options.RequireHttpsMetadata = false; // Dev env only
}
Existing APIs
There is a bunch of existing Web APIs that connect to this Azure AD. Sadly, they are all using the full .NET Framework. They use the UseWindowsAzureActiveDirectoryBearerAuthentication method from the Microsoft.Owin.Security.ActiveDirectory namespace's WindowsAzureActiveDirectoryBearerAuthenticationExtensions.
Another thing they use is the HostAuthenticationFilter with an authentication type of Bearer.
Questions
What is the problem?
How can I resolve this issue?
How can I use these components together?
ASP.NET Core 2.2
JWT Bearer Authentication
Azure AD (token validation + claim extraction only - creation is handled by other service)
You are using OpenIDConnect libraries and point them to WS-Federation metadata (/federationmetadata/2007-06/federationmetadata.xml). This is not going to work.
The correct metadata endpoint for OpenIDConnect is /.well-known/openid-configuration. This is described here. Change that first, and then return cookies.
UPDATE
What I oversaw, was that you are protecting WebAPI. You say the middleware to use JwtBearer as default authentication cheme, but you also include a challenge scheme to be OIDC. That doesn't really make sense for me. Why do you want an OIDC challenge scheme for an WebAPI?
Here you can find the ASP.NET Core samples about JwtBearer. Here the Azure AD samples demoing WebApp calling WebApi (also bearer for the WebAPI, OIDC for the App FrontEnd.
There are no samples for JWT Bearer Auth using OIDC challenge. Why do you want to implement that? What is the case? You might be looking at implementing multiple Authentication schemes, which is possible. But not having one scheme for Authentication and another for challenge...
If by updating/removing the wrong metata changes the error message, include that in your original question. As it is now - the pure error message is that OIDC Middleware cannot parse WS-Federation metadata. Which is expected.
Source of the problem
After some testing I managed to identify the problem: Apparently the main cause of this issue was network related. When I switched from our company's to an unrestricted network the authentication was a success.
The fix
I had to configure a proxy and provide it to the JwtBearer and OpenIdConnect middleware. This looks like this:
var proxy = new HttpClientHandler
{
Proxy = new WebProxy("{ProxyUrl}:{ProxyPort}") { UseDefaultCredentials = true; },
UseDefaultCredentials = true
};
services
.AddJwtBearer(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
.AddOpenIdConnect(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
Metadata adress
#astaykov was right that the metadata adress is indeed incorrect. I had this feeling as well but kept it as previous APIs were running successfully with it. During problem testing I removed it, too, but it would not make a difference due to the network issues.
After the network issues were resolved, using the default metadata adress worked. The custom one failed - as expected when using a different authentication schema.
I'm trying to use Identity Server 4 to protect my API. Now I have gone through all the documentation at http://docs.identityserver.io/en/release/quickstarts/1_client_credentials.html and I have set up a few successful demos. However, there is one thing that I am failing to understand.
For example, First, we need to define a client on the IS4 that looks like this:
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
Then on the API we protect it by adding the IdentityServer4.AccessTokenValidation package and adding configuration to startup.cs
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
And then finally we add app.UseAuthentication();
Now, this all works when ran, however, the part that I don't understand is where is the secret defined in the API. As you can see, the client clearly expects a secret and yet I don't define this secret anywhere on my API. I also don't define anywhere on the IS4 anything about my API to say that you are protecting the API from this URI or something along those lines.
So how does this actually works in terms of IS4 knowing about the API and authenticating its requests?
EDIT:
To clarify some confusion, yes there is a client that I opted out in code above and I see now that I shouldn't, and in there I provide a secret, but I'm still not understanding how does the IS4 knows to protect my specific API. What if the request came from www.somerandomapi.com? From what I"m reading it would work regardless. Based on what you wrote, it does make sense that the client is passing the secret, but nowhere in my code is the IS4 told which API to protect.
I have two .net applications.
Both applications have WebAPI 2.O APIs using C#.
Let's say one is parent application another one is a child.
Parent application has Owin authentication and all APIs working as expected with Authorization.
In child application, I want to use same Authorization provider used in the parent application. I don't want to use authentication for child application again.
Two things I have tried:
Use of same machine keys in both the applications
Tried to create a third independent .net application which will provide authentication and authorization for both the applications.
First one didn't work. I am not sure how I can achieve the second one.
Any help would be appreciated.
Thanks.
So, if I understood correctly, you want a way to authenticate a child service, based on the parent service authentication passing authentication between the services.
We just need the same thing here, to authenticate the microservices behind our front service (parent service).
We used JWT for that, using it we can solve that, because on the child services (in our case microservices) they trust the parent authentication.
The services work like this, the Parent Service or maybe another Authentication service creates the valid JWT to be used on the Parent Service.
When the Parent Service, receveives the JWT they will validate everything that's need to ensure the client is corret. When the Parent Service need to call the Child Service, it'll send the same JWT, but on the Child Service the JWT will be not the same, in our case we just validate the Lifetime and Issuer Sign Key.
We end up with a code like this on our Startup.cs file on our child services, our parent service/auth service was kept the same.
public static void ConfigureAuth(IServiceCollection services)
{
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
// Must validate the signing key
ValidateIssuerSigningKey = true,
// Must validate the life time
ValidateLifetime = true,
// The issuer may vary in a multitenant scenario,
// that's why we not valid the issuer.
ValidateIssuer = false,
ValidIssuer = o.ClaimsIssuer,
// Allowing passing a token among multiple services (audiences).
ValidateAudience = false,
ValidAudience = "",
// Does not require expiration
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero
};
});
}
If you still have doubts I recommend you to look for Authentication Between Microservice, maybe that can help.
Store the generated authentication token (along with user identity info if needed) from the Parent application in a secure Redis cache.
You can then get the token from subsequent requests on the Parent API's authorized endpoints, and append it on any calls to your Child API:
public class ValuesController : ApiController
{
[Authorize]
public IHttpActionResult Get()
{
var authToken = Request.Headers.Authorization;
// send authToken with requests to child endpoints
}
}
Then on the Child API you can get the auth token in a similar manner, and lookup & validate it against the stored Redis tokens.
Extra points if you're getting the token in middleware.
I've succeeded with IdentityServer3 implementation. I created ASP.NET MVC5 client with implicit flow and this Owin startup configuration:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{ AuthenticationType = "Cookies" });
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc.client",
Authority = Constants.ServerUri,
RedirectUri = Constants.MvcClientUri,
ResponseType = "id_token",
Scope = "openid profile",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{ SecurityTokenValidated = TokenValidatedNotification },
});
Everything works perfect running locally on IIS Express, but fails when deployed to production IIS server. Authentication is successful in both cases - id_token is returned from then same IdentityServer, but on production user is never authenticated, notification is not fired. Project references Microsoft.Owin.Host.SystemWeb assembly. I've registered exception handling OwinMiddleware as first one, but no unhandled exception was catched.
Make sure the production IIS has the website configured to use Anonymous authentication, as you are not relying on IIS's authentication infrastructure, you want the request to flow through IIS and get to the OWIN middleware responsible for authentication.