User-authorization on API-side with .net core - c#

I'm writing a web-application in .net core that uses an API and a Website.
The web-service builds a JWT-token.
This is the service-configuration (removed unnecessary parts)
public void ConfigureServices(IServiceCollection services)
{
//...
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "ExampleIssuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "ExampleAudience",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero,
};
var serialiser = services.BuildServiceProvider().GetService<IDataSerializer<AuthenticationTicket>>();
var dataProtector = services.BuildServiceProvider().GetDataProtector(new string[] {$"IronSphere.Web.Site-Auth"});
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = tokenValidationParameters;
})
.AddCookie(cookie =>
{
cookie.Cookie.Name = "access_token";
cookie.TicketDataFormat = new JwtTokenValidator(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters, serialiser, dataProtector);
});
//...
}
So far, so good. The login works, my website-side authorization works, I can work with the [Authorize] attribute.
The problem is now, I'm logged in on the website, but not on the API.
I can't use the [Authorize]-attribute for my API methods (and, yes, for sure, it makes sense).
So when I'm logged in, everytime I call the API, I also send the token in the header (this works, I can read it in the API-controller). I guess I'd need to deserialize it.
I tried with dependency injection to get that IDataSerializer<AuthenticationTicket> into my controllers with:
services.AddSingleton<IDataSerializer<AuthenticationTicket>>(services.BuildServiceProvider().GetService<IDataSerializer<AuthenticationTicket>>());
then take the key from header, deserialize and I'd have my user-object with claims. But when I try to inject in any controller it makes my application crash (just a message, that dotnet stopped working)
Any idea how I can also validate the user when the api is getting called? (I could post more code if you need, just didn't want to fill here with too much code)

Related

Configuring different authorization/authentication schemes

I am implementing security on an ASP.NET Core 1.0.1 application, which is used as a Web API. I am trying to understand if and how to implement 2 different authentication schemes.
Ideally, I would like to allow authentication via Azure Active Directory or via username/password for specific back-end services that contact the application.
Is it possible to configure ASP.NET Core for such a setup where an endpoint either authenticates through Azure AD or JWT token?
I tried with something like this, but upon calling the generate token endpoint, I get a 500 with absolutely no information. Removing the Azure AD configuration makes the endpoint work perfectly:
services.AddAuthorization(configuration =>
{
configuration.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAD:ClientId"],
Authority
= Configuration["Authentication:AzureAd:AADInstance"]
+ Configuration["Authentication:AzureAd:TenantId"],
ResponseType = OpenIdConnectResponseType.IdToken,
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme
});
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromMinutes(1),
IssuerSigningKey = TokenAuthenticationOptions.Credentials.Key,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = TokenAuthenticationOptions.Audience,
ValidIssuer = TokenAuthenticationOptions.Issuer
}
});
Use the OpenIdConnectDefaults.AuthenticationScheme constant when you add the authorization policy and when you add the authentication middleware.
Here you are using OpenIdConnectDefaults. Good. Keep that line.
services.AddAuthorization(configuration =>
{
...
configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme) // keep
.RequireAuthenticatedUser().Build());
});
Here you are using CookieAuthenticationDefaults. Delete that line.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme // delete
});
Why?
When your OpenIdConnect authorization policy runs, it will look for an authentication scheme named OpenIdConnectDefaults.AuthenticationScheme. It will not find one, because the registered OpenIdConnect middleware is named CookieAuthenticationDefaults.AuthenticationScheme. If you delete that errant line, then the code will automatically use the appropriate default.
Edit: Commentary on the sample
A second reasonable solution
The linked sample application from the comments calls services.AddAuthentication and sets SignInScheme to "Cookies". That changes the default sign in scheme for all of the authentication middleware. Result: the call to app.UseOpenIdConnectAuthentication is now equivalent to this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme
}
That is exactly what Camilo had in the first place. So why did my answer work?
My answer worked because it does not matter what SignInScheme name we choose; what matters is that those names are consistent. If we set our OpenIdConnect authentication sign in scheme to "Cookies", then when adding an authorization policy, we need to ask for that scheme by name like this:
services.AddAuthorization(configuration =>
{
...
configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme) <----
.RequireAuthenticatedUser().Build());
});
A third reasonable solution
To emphasize the importance of consistency, here is a third reasonable solution that uses an arbitrary sign in scheme name.
services.AddAuthorization(configuration =>
{
configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Foobar")
.RequireAuthenticatedUser().Build());
});
Here you are using CookieAuthenticationDefaults. Delete that line.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
SignInScheme = "Foobar"
});

ASP.NET Core 2.0 - Multi-tenant JWT Signing Key

I'm building a web API in ASP.NET Core 2.0 and am using JWT as the authentication mechanism. So, I'd usually have some code like the following in Startup.ConfigureServices():
...
string secret = configuration.GetSection("TokenAuthentication:Secret").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret));
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = configuration.GetSection("TokenAuthentication:Issuer").Value,
ValidateAudience = true,
ValidAudience = configuration.GetSection("TokenAuthentication:Audience").Value,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
...
However, the secret varies for each tenant. So, ideally I'd lookup the secret from a database table ...
I'm struggling to see how to do this because the above code sets the secret when all the dependencies are registered - I need to do this on each request.
I wondered whether I was missing something simple - like a delegate that I can implement to do the secret lookup ... or whether I needed to roll my own JWT validator (not keen on this!)
Any help appreciated!

Authentication and Claims Transformation Using OpenIdConnect in an ASP.NET Core 2.0 API

I don't really know what I'm doing here and have found it very difficult to research. I need to secure an API which is going to be consumed by a Mobile application (specifically, an Ionic app).
There is an WS02 Identity Server (unfortunately, not the Thinktecture Identity Server :)) which provides a token endpoint.
But I'm not really sure how I go about doing all the things an API should do for the OpenIdConnect Authorization Code flow.
The code I have so far does not achieve much i.e. the User object is not populated with the user in the Token (which I'm sending as a header via postman - i.e. using Authorization as the key with a value of bearer followed by the token string).
Here is that code:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
//options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
//options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
//options.DefaultSignInScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer("", options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidIssuer = "[base url of identity server]"
};
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "[base url of identity server]";
options.ClientId = "kfgujhfdlghjkfdlhgjkfdlvzKK";
options.ClientSecret = "hKFqyurievbgytoreyerGSD";
options.ResponseType = OpenIdConnectResponseType.Token;
options.CallbackPath = "/";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.Scope.Add("api");
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = e =>
{
var ctx = e;
return Task.CompletedTask;
}
};
});
Note: I put a breakpoint in the OnTicketReceived event. But it never gets hit.
Also, every example around seems to use cookies middleware.
I've tried that as well with no luck. But I did not think it was really very viable for a mobile app which is not browser-based. That's why the defaultscheme stuff is commented out as it seemed to be cookies-centric.
Thanks

Asp.net core 2 - 401 error with bearer token

I'm not able to access protected method with Authorized with a token generated by Asp.net Core.
The configuration :
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.Audience = Configuration["Tokens:Issuer"];
cfg.ClaimsIssuer = Configuration["Tokens:Issuer"];
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
The token generated :
var claims = new[] {
new Claim (JwtRegisteredClaimNames.Sub, model.Email),
new Claim (JwtRegisteredClaimNames.Jti, Guid.NewGuid ().ToString()),
};
//_config
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiration = DateTime.UtcNow.AddDays(7);
var token = new JwtSecurityToken(_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
claims,
expires: expiration,
signingCredentials: creds);
return new TokenModel()
{
Token = new JwtSecurityTokenHandler().WriteToken(token),
Expiration = expiration,
UserFirstName = model.FirstName,
UserLastName = model.LastName
};
After the generation I get this kind of token :
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZWl4ZWlyYXBlcnNvQGdtYWlsLmNvbSIsImp0aSI6IjVmNTk3OGVkLWRlZjAtNDM3Yi1hOThhLTg3ZWU4YTQ3MmZlNCIsImV4cCI6MTUxODg2ODYxOCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIn0.1fHXr8jtuZ8PTJmJPBKQIqiOk_c-bCQ6KRyFLLJkU5s",
"expiration": "2018-02-17T11:56:58.683076Z",
"userFirstName": null,
"userLastName": null
}
I can add or not the autorization in my HTTP headers in Postman, I receive an "Unauthorized Exception - 401"
I already check some other Stack post and GitHub Post, It seems my configuration it's ok.
If needed I can add the configuration file.
Thanks.
Edit 1 :
Here the screen of the header in postman :
Your code looks OK. The most possible root cause of the problem is that you have not added authentication middleware to your application. AddAuthentication extension call for IServiceCollection just registers all required services, but it does not add authentication middleware to HTTP request pipeline.
To fix the problem add following call in Startup.Configure() method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// ...
}
I was able to reproduce the problem with your code, and calling app.UseAuthentication() fixes the issue.

Using Claims with OpenIdConnect.Server in ASP.NET 5

In the past 7 days I've tried to setup an ASP.NET 5 WebApi using OpenIdConnect.Server with the resource owner flow.
I was more or less successful in generating a token and accessing [Authorize] protected actions.
However, when I try to access this.User.Identity.Claims, it's empty. I am using ASP.NET 5, beta6 for now (having troubles upgrading to most recent beta7 and waiting for it's official release)
In the Startup.cs I got the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddCaching();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<AuthContext>(options =>
{
options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString"));
});
services.AddIdentity<AuthUser, AuthRole>(
options => options.User = new Microsoft.AspNet.Identity.UserOptions
{
RequireUniqueEmail = true,
UserNameValidationRegex = "^[a-zA-Z0-9#_\\.-]+$"
})
.AddEntityFrameworkStores<AuthContext, Guid>()
.AddDefaultTokenProviders();
services.ConfigureCors(configure =>
{
configure.AddPolicy("CorsPolicy", builder =>
{
builder.WithOrigins("http:/localhost/", "http://win2012.bludev.com/");
});
});
services.AddScoped<IAuthRepository, AuthRepository>();
}
public void Configure(IApplicationBuilder app)
{
var factory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
factory.AddConsole();
app.UseStaticFiles();
app.UseOAuthBearerAuthentication(options =>
{
options.Authority = "http://win2012.bludev.com/api/auth/";
options.Audience = "http://win2012.bludev.com/";
options.AutomaticAuthentication = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
RequireSignedTokens = true,
RoleClaimType = ClaimTypes.Role,
NameClaimType = ClaimTypes.NameIdentifier,
ValidateActor = true,
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateSignature = true,
ValidAudience = "http://win2012.bludev.com/",
ValidIssuer = "http://win2012.bludev.com/"
};
});
app.UseOpenIdConnectServer(options =>
{
options.Issuer = new Uri("http://win2012.bludev.com/api/auth/");
options.AllowInsecureHttp = true;
options.AuthorizationEndpointPath = PathString.Empty;
options.Provider = new AuthorizationProvider();
options.ApplicationCanDisplayErrors = true;
// Note: in a real world app, you'd probably prefer storing the X.509 certificate
// in the user or machine store. To keep this sample easy to use, the certificate
// is extracted from the Certificate.pfx file embedded in this assembly.
options.UseCertificate(
assembly: typeof(Startup).GetTypeInfo().Assembly,
resource: "AuthExample.Certificate.pfx",
password: "Owin.Security.OpenIdConnect.Server");
});
app.UseIdentity();
app.UseMvc();
}
}
I used app.UseOAuthBearerAuthentication because I couldn't get app.UseOpenIdConnectAuthentication working, all I would get is this in the console:
request: /admin/user/ warning :
[Microsoft.AspNet.Authentication.OpenIdConnect.OpenIdConnectAuthentica
tionMiddleware] OIDCH_0004: OpenIdConnectAuthenticationHandler:
message.State is null or empty. request:
/.well-known/openid-configuration warning :
[Microsoft.AspNet.Authentication.OpenIdConnect.OpenIdConnectAuthentica
tionMiddleware] OIDCH_0004: OpenIdConnectAuthenticationHandler:
message.State is null or empty.
and an Exception after the time out
error : [Microsoft.AspNet.Server.WebListener.MessagePump]
ProcessRequestAsync System.InvalidOperationException: IDX10803: Unable
to create to obtain configura tion from:
'http://win2012.bludev.com/api/auth/.well-known/openid-configuration'
. at Microsoft.IdentityModel.Logging.LogHelper.Throw(String
message, Type excep tionType, EventLevel logLevel, Exception
innerException) at
Microsoft.IdentityModel.Protocols.ConfigurationManager`1.d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
...
With this configuration UseOpenIdConnectAuthentication
app.UseOpenIdConnectAuthentication(options =>
{
options.AuthenticationScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;
options.Authority = "http://win2012.bludev.com/api/auth/";
options.Audience = "http://win2012.bludev.com/";
options.Resource = "http://win2012.bludev.com/";
options.AutomaticAuthentication = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
RequireSignedTokens = true,
RoleClaimType = ClaimTypes.Role,
NameClaimType = ClaimTypes.NameIdentifier,
ValidateActor = true,
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateSignature = true
};
});
So the real question is:
How to get resource owner flow to work with claims
ValidateLifetime = true or ValidateAudience = true would throw exception and result in a Http Code 500 response without a printed error.
How to turn authentication failures into a meaningful 400/403 code and a json or xml respones (depending on the client preference) to be displayed for the user? (JavaScript is the client in this case)?
app.UseOpenIdConnectAuthentication() (which relies on OpenIdConnectAuthenticationMiddleware) is only meant to support interactive flows (code/implicit/hybrid) and cannot be used with the resource owner password credentials grant type. Since you only want to validate access tokens, use app.UseOAuthBearerAuthentication() instead.
See this SO answer for more information about the different OpenID Connect/OAuth2 middleware in ASP.NET 5: Configure the authorization server endpoint
How to get resource owner flow to work with claims
The entire OpenIdConnectServerMiddleware you're using is based on claims.
If you have trouble serializing specific claims, remember that all claims except ClaimTypes.NameIdentifier are not serialized by default in the identity and access tokens, since they are both readable by the client application and the user agent. To avoid leaking confidential data, you need to specify an explicit destination indicating where you want the claims to be serialized:
// This claim will be only serialized in the access token.
identity.AddClaim(ClaimTypes.Name, username, OpenIdConnectConstants.Destinations.AccessToken);
// This claim will be serialized in both the identity and the access tokens.
identity.AddClaim(ClaimTypes.Surname, "Doe",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken););
ValidateLifetime = true or ValidateAudience = true would throw exception and result in a Http Code 500 response without a printed error.
How to turn authentication failures into a meaningful 400/403 code and a json or xml respones (depending on the client preference) to be displayed for the user? (JavaScript is the client in this case)?
That's how the OIDC client middleware (managed by MSFT) currently works by default, but it will be eventually fixed. You can see this GitHub ticket a workaround: https://github.com/aspnet/Security/issues/411

Resources