I'm using the IdentityServer4 "AspNetCoreAndApis" sample application found here
It has a token server and an MVC client application.
The identity server project has an external OIDC authentication provider set up using their demo server - https://demo.identityserver.io/
After hitting a protected endpoint in MvcClient, being redirected to the local identity server, choosing and authenticating with the demo server, it reaches the ExternalController callback of the local identity server. At this point I would like to issue additional claims to the user, and have them be available in MvcClient.
There's code in the callback to addadditionalLocalClaims and issue a cookie. I tried adding another claim:
var additionalLocalClaims = new List<Claim>();
additionalLocalClaims.Add(new Claim("TestKey", "TestValue"));
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray());
But by the time the user arrives in the HomeController of MvcClient this claim is not there.
I think I don't properly understand which authentication scheme is being used where, and the function of the relevant cookies.
EDIT:
In response to the first comment below, I tried attaching a claim to a requested scope, but still no luck - this is the in memory resource store:
public static IEnumerable<ApiResource> Apis
{
get
{
var apiResource = new ApiResource("api1", "My API");
apiResource.UserClaims.Add("TestKey");
var resources = new List<ApiResource>
{
apiResource
};
return resources;
}
}
The MvcClient is both allowed the api1 scope, and requests it.
Your client MVC could get the user's custom claims from ID token or UserInfo endpoint .
To add claims to ID token , you can set client's config :AlwaysIncludeUserClaimsInIdToken . But involve all user claims in ID token is not recommended concern about the size of ID Token .
A better solution is making your client app get user's claims from UserInfo endpoint :
public class MyProfileService : IProfileService
{
public MyProfileService()
{ }
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>()
{
new Claim("TestKey", "TestValue")
};
context.IssuedClaims.AddRange(claims);
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context)
{
// await base.IsActiveAsync(context);
return Task.CompletedTask;
}
}
Register in DI :
services.AddTransient<IProfileService, MyProfileService>();
The IProfileService service could be used to add claims to ID Token, Access token and UserInfo endpoint . By default the custom claims won't involve in ID Token event using IProfileService , the reason explained above - the ID token size . So you can make your client app get claims from UserInfo endpoint with OIDC middleware config :
options.Scope.Add("profile");
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("TestKey", "TestKey");
Above codes will add OIDC profile permission to get claims from endpoint , and send a request to connect/userinfo endpoint with ID Token , and get claims and map claim whose name is TestKey to your client's claim principle and save to cookie . Now you can get the claims with User.Claims in MVC .
Related
Our roles model is different so we can't use the stock Microsoft identity database model and all UX that goes with it, more's the pity.
All I want to do is
use OpenIdDict
have AzureAd do authentication
put my own claims into the claims principal so they go into the identity token when OpenIdDict creates it
I'm not interested in IdentityServer for assorted reasons.
I worked through a tutorial and had no trouble building all this using cookie based authn handled in an AccountController but I cannot figure out how to switch over to Azure and could really use some help.
Startup looks like this
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
// .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
// {
// options.LoginPath = "/account/login";
// });
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
// from package `Microsoft.Identity.Web`
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store.
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
services.AddHostedService<TestData>();
var openiddictBuilder = services.AddOpenIddict();
// Register the OpenIddict core components.
openiddictBuilder.AddCore(options =>
{
// Configure OpenIddict to use the EF Core stores/models.
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
});
// Register the OpenIddict server components.
openiddictBuilder.AddServer(options =>
{
options
.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange()
.AllowClientCredentialsFlow()
.AllowRefreshTokenFlow()
.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token")
// Encryption and signing of tokens
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption()
// Register scopes (permissions)
.RegisterScopes("api")
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.EnableAuthorizationEndpointPassthrough()
;
});
}
There's an AuthorizeController with an Authorize method that looks like this
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the user principal stored in the authentication cookie.
// var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// If the user principal can't be extracted, redirect the user to the login page.
if (!result.Succeeded)
{
var authprops = new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
};
return Challenge(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: authprops);
}
// Create a new claims principal
var claims = new List<Claim>
{
// 'subject' claim which is required
new Claim(OpenIddictConstants.Claims.Subject, result.Principal.Identity.Name),
new Claim(OpenIddictConstants.Claims.Role,"admin").SetDestinations(
OpenIddictConstants.Destinations.IdentityToken),
new Claim(OpenIddictConstants.Claims.Role,"gerbil wrangler").SetDestinations(
OpenIddictConstants.Destinations.IdentityToken)
};
var claimsIdentity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
// Set requested scopes (this is not done automatically)
claimsPrincipal.SetScopes(request.GetScopes());
// Signing in with the OpenIdDict authentiction scheme causes OpenIdDict
// to issue a code which can be exchanged for an access token
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
As I understand the theory of operation, OpenIddict proxies authentication and then issues a new token. That implies that the AzureAd redirect_uri ought to be set to an endpoint provided by OpenIddict, probably something like signin-openiddict and assuming that I'm right about all that, the client talking to OpenIddict will in turn provide a completely distinct and unrelated redirect_uri. But I haven't found any documentation covering this yet, so feel free to educate me.
In OpenIddict (and IdentityServer) the login and token generation are separated.
Those 2 parts are generally:
The user logs in using arbitrary methods and the authentication cookie is created.
The token endpoint reads the authentication cookie and creates tokens.
To use Azure Ad Authentication, you need to alter the first part to generate an authentication cookie using Azure Ad. To achieve this you'll need to implement the ExternalLogin and ExternalLoginCallback as seen in this example: https://github.com/openiddict/openiddict-core/blob/cda55862bcba67bf3de4ba08cf512ee9e2269cf5/samples/Mvc.Server/Controllers/AccountController.cs#L141
Instead of using the SignInManager, you need to create the authentication cookie yourself in the ExternalLoginCallback method. This can be done by using the HttpContext.SignInAsync method.
The second part (token generation) is left unchanged and should work without modification.
I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.
On Owin middleware Startup class I have added OIDC Authentication where response type is "code id_token". With this middleware I can access my authorized controller. But the problem is, I can't access my API in the same domain with this middleware.
I am using the access_token that i stored in the userClaim. But it is returning the HTML of IdentityServer4 login page.
[Filters.AuthorizeOIDC(Roles = "dukkan.sa")]
public async Task<ActionResult> ViewApiResult()
{
var user = User as System.Security.Claims.ClaimsPrincipal;
var token = user.FindFirst("access_token").Value;
var result = await CallApi(token);
ViewBag.Json = result;
return View();
}
private async Task<string> CallApi(string token)
{
var client = new HttpClient();
client.SetBearerToken(token);
var json = await client.GetStringAsync("http://localhost:57346/api/SampleApi");
return json;
}
The examples I got to secure MVC API is with IdentityServer3. They are using IdentityServer3.AccessTokenValidation package to authenticate the client from back channel during the API Access request:
app.UseOAuthBearerAuthentication(new IdentityServerBearerTokenAuthenticationOptions { Authority = "https://localhost:44319/identity", RequiredScopes = new[] { "sampleApi" } });
But IdentityServer4.AccessTokenValidation is not working with MVC5. I can use IdentityServer3.AccessTokenValidation in MVC 5. But this is accepting IdentityModel with version bellow 2.0.0.
Need solution for it. IdentityServer4 is not supporting properly for MVC.
Why do you want to use IdentityServer4.AccessTokenValidation with MVC5? Because the server is IdentityServer4?
There is no need for that. IdentityServer3 and IdentityServer4 are build on the same OpenId Connect specifications, meaning that you can use IdentityServer3.AccessTokenValidation for the client while the server is IdentityServer4.
In fact you can use any piece of code on the client that is build according to the specifications of OpenId Connect. I suggest you give IdentityServer3.AccessTokenValidation a try.
We're evaluating service stack v.4.5.6.0 for a Web API and we want clients to be able to authenticate using basic auth or credentials but we do not want them to be able to provide a basic auth header in place of a JWT token or session cookie when using our services. While I realize this is somewhat arbitrary, is there a way to exclude routes from specific providers or force the use of a token/cookie to authenticate once they've logged in?
Auth config from AppHost:
private void ConfigureAuth(Container container)
{
var appSettings = new AppSettings();
this.Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[]
{
new CredentialsAuthProvider(),
new BasicAuthProvider(),
new JwtAuthProvider(appSettings)
}) { IncludeAssignRoleServices = false, MaxLoginAttempts = 10} );
var userRepository = new CustomUserAuthRepository(container.TryResolve<IDbConnectionFactory>());
container.Register<IAuthRepository>(userRepository);
}
ServiceStack lets you decide which AuthProviders you want your Services to be authenticated with, but it doesn't let you individually configure which adhoc AuthProviders applies to individual Services. Feel free to add this a feature request.
However if you want to ensure that a Service is only accessed via JWT you can add a check in your Services for FromToken which indicates the Session was populated by a JWT Token, e.g:
[Authenticate]
public class MyServices : Service
{
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (!session.FromToken)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
}
From v4.5.7 that's now available on MyGet you can also use the new session.AuthProvider property which indicates what AuthProvider was used to Authenticate the user, e.g:
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (session.AuthProvider != JwtAuthProvider.Name)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
Refer to the docs for different AuthProvider names for each AuthProvider.
I want to create Custom Bearer Token, with some additional information to be store in the token.
Just want to Use Create Token functionality.(something like FormsAuthentication) without using default implementation(ASP.NET Identity) of User Tables.
1) Custom Login method(MyLogin), that will create custom bearer token with additional information(IP Address embedded into token).
2) on subsequent request be able to inspect the additional information and reject(treat the request as unauthenticated) if the additional information does not match some rule.
In case i receive the bearer token and find the request is coming from different IP address then the one embedded inside it, clear/Invalidate the Bearer Token and treat the current request as UnAuthenticated.
I'm by no means an expert but this is the information i gathered.
This seems to be relatively simple to do with ASP.NET Identity.
You need to create your own implementation of a token provider which implements the IAuthenticationTokenProvider interface. You implement the create method so it creates the token just the way you want and then you supply your provider when configuring the authentication middleware.
The configuration in your starup class would look something like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
//Rest of code is here;
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/yourtokenendpoint"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
AccessTokenProvider = new YourCustomTokenProvider() // YourCustomTokenProvider implements IAuthenticationTokenProvider
};
OAuthBearerAuthenticationOptions bearerOptions = new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = new YourCustomTokenProvider() // YourCustomTokenProvider implements IAuthenticationTokenProvider
}
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(bearerOptions);
}
}
I have never done this myself but I hope this was of some use.
EDIT: To validate the the token you could create a custom action filter that you decorate your controller actions with. In this action filter you could validate the token and do whatever you like with the request. See this guide.