IdentityServer and Microservices: Missing Sub Claims - microservices

I am having this error after logging from the identity server.
InvalidOperationException: sub claim is missing
IdentityServer4.Hosting.IdentityServerAuthenticationService.AssertRequiredClaims(ClaimsPrincipal principal) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerAuthenticationService.cs, line 119
I am using implicit grant types for my web api running with swagger ui. Everything seems fine when I typed in Client Id and Scope in the popup. It redirect me to Identity Server login screen. Once typed in the login details, this error shown.
I think my configured in my web api and identity server should be fine. I am thinking may be some claim missing in my identity server database.
new Client
{
ClientId = "parent.api.gateway",
ClientName = "Parent Api Gateway",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5100/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { "http://localhost:5100/swagger/" },
AllowedScopes =
{
"api1"
}
},
The above code is a inMemory of my setting, I put it inside the database.

Related

Identity server 4 reference token with MVC

I am using Identity server 4(with entity-framework for configs) and defining a MVC client with reference token (AccessTokenType=1). I can login to IS4 by using the client and defined user and get access token (reference type). I know that this token does not contains claims but I have all claims in Security.Claims.ClaimPrincipal. Is it getting claims by doing behind the scene request to IS4?
I have 2 main issues:
1) I set the access token life time to 10 mins for MVC client, and cookie is valid for 450 hours. I expect that after 10 mins user redirected to login page on IS4 as access token is expired but it is not happening
2) Also when I remove PersistedGrants from database, still I am logged in and can see MVC client, Why?
Should I do anything in middleware on MVC client to check access token by using reference token?
I need this for forcing user to login on all logged in clients again.
this my MVC setting:
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromHours(750),
AutomaticChallenge = true
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:7010",
RequireHttpsMetadata = false,
ClientId = "MVC_Client",
ClientSecret = "MVC_Client",
ResponseType = "code id_token",
Scope =
{
Common.Constants.IdentityManagement.OpenIdScopeName,
Common.Constants.IdentityManagement.ProfileScopeName,
Common.Constants.IdentityManagement.EmailScopeName,
},
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
TokenValidationParameters =
new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
},
Events = new OpenIdConnectEvents()
{
OnTicketReceived = OnTicketReceived
}
});
The client has two grant types: hybrid,client_credentials
And this is client properties in databse:
[AbsoluteRefreshTokenLifetime]: 60
[AccessTokenLifetime]: 60
[AccessTokenType]: 1
[AllowAccessTokensViaBrowser]: False
[AllowOfflineAccess]: True
[AllowPlainTextPkce]: False
[AllowRememberConsent]: True
[AlwaysIncludeUserClaimsInIdToken]: False
[AlwaysSendClientClaims]: False
[AuthorizationCodeLifetime]: 60
[ClientId]: MVC_Client
[ClientName]: MVC_Client
[ClientUri]: NULL
[EnableLocalLogin]: True
[Enabled]: True
[IdentityTokenLifetime]: 60
[IncludeJwtId]: False
[LogoUri]: NULL
[LogoutSessionRequired]: True
[LogoutUri]: NULL
[PrefixClientClaims]: True
[ProtocolType]: oidc
[RefreshTokenExpiration]: 60
[RefreshTokenUsage]: 1
[RequireClientSecret]: True
[RequireConsent]: False
[RequirePkce]: False
[SlidingRefreshTokenLifetime]: 60
[UpdateAccessTokenClaimsOnRefresh]: False
I've solved the similar issue and I set the life time of the cookie according the life time of access token and after refreshing of access token it will be renew the cookie.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
SaveTokens = true,
UseTokenLifetime = false,
Events = new OpenIdConnectEvents()
{
OnTicketReceived = n => OnTicketReceived(n)
}
});
private Task OnTicketReceived(TicketReceivedContext n)
{
var accessTokenExpiresAt = n.Properties.Items[".Token.expires_at"];
n.Properties.ExpiresUtc = DateTimeOffset.Parse(accessTokenExpiresAt);
return Task.FromResult(0);
}
Another way how you can manage the cookie is used the following method OnValidatePrincipal:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = LastChangedValidator.ValidateAsync
}
});
public static class LastChangedValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
// Check if user is still valid
if (!isUserValid)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
The following code is from here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore1x
Thanks Jenan.
Set cookie expiry to token expiry works.
Another issue I have is that when I remove the reference tokens from PersistedGrants and after 10 mins (access token lifetime) the cookie is expired and I expect that user get redirected to login page. But MVC client goes to IS and IS creates new reference token without authentication. How it is possible?
In the IdentityServer log I see this:
[Microsoft.AspNetCore.Hosting.Internal.WebHost] Request starting HTTP/1.1 GET http://localhost:7010/connect/authorize?client_id=IdentityManagement&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20email%20Roles%20IdentityManagement%20IdentityUsers&response_mode=form_post&nonce=636452284146749770.ZTk3YzFiM2QtYTQxMi00MGI3LWJjMGEtMWFkOGRhYTE0ZjE3NDhjZGE3MjMtYTczNS00Y2ZkLThhOTctNzAxYmM4NTY4MjE5&state=CfDJ8HzK9L_BsbZJtObgOKdlRawJPSBTZc1UETnT9osu2OIOojB6vxT7t7GjIBO2nf2TYngPk3u8EcDMk8o_dVUvj8VTaEQf0s1DvTUwaxZn93_TKv1waoFukeEBFwaSB1yWTbNq62dyYkLc6_fkiW4r16BwFyKpVEvaMmh2NGLUmfFiQ-7qj6f4VyR3pM0ydd7Ah8Vs6BIfXlyqtQJ4Ak4sR1jrcGO9-ViTWCFe2YN0M9-OWFluiFQOylh4quzwseYjjOgY0ruVCwK7Lw1pvVMewnn_f2uiXk7QXBz7TMYcp8kylbdgL5Vx0fSBrB67nKSER5m-gjPXNIky6FrBPSouqzw
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] HttpContext.User merged via AutomaticAuthentication from authenticationScheme: "idsrv".
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
[IdentityServer4.Hosting.IdentityServerMiddleware] Invoking IdentityServer endpoint: "IdentityServer4.Endpoints.AuthorizeEndpoint" for "/connect/authorize"
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
Why IdentityServer creates new reference token without authentication. The previous reference token is removed from PersistedGrants.
I am doing this for forcing user to login again in case of emergency (losing device,...)
Correct, I used this for set the IS cookie:
var props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(120)
};
await HttpContext.Authentication.SignInAsync(userCredential.User.Id.ToString(), userCredential.User.UserName, props);
But I do not want to set it the same as client cookie expiry as it affects SSO. Also if I set the IS cookie greater than client cookie the other issue happens (as IS cookie is valid IS creates new reference token without authentication).
Now I am confused!

Adding Claims to Identity from my WebApi

I am currently developing a Web Application that contains a Angular4 UI working with IdentitServer4 and a WebApi dotnetcore application.
We have the application authentication mechanism working with IDS but we now want to limit parts of our Angular application based on what permissions a user has granted them. This information is stored behind our WebApi. These permissions would also be used to secure our WebApi from stopping users doing particular actions if they aren't allowed i.e. EditUsers.
the problem I am facing is that ideally after being authentication by IdentityServer I would like the Angular application to fetch the list of allowed
permissions and from there they send those up to the WebApi as part of their claims. The reason for this is that I don't want to have to query the database
on each Api Call if I can help it just to see if a particular user has access to a particular Controller action.
Is there anyway that I can set these claims so that subsequent calls to the API contains these and from there I can just check the claim information on the User Claim Identity
to verify access to a resource?
You can add scope to your WebApi (official docs) for example
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "https://demo.identityserver.io",
ApiName = "api1",
AllowedScopes = { "api1.read", "api1.write" }
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
And you can add claims to the client application as:
var mvcClient = new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientUri = "http://identityserver.io",
AllowedGrantTypes = GrantTypes.Hybrid,
AllowOfflineAccess = true,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/" },
LogoutUri = "http://localhost:5002/signout-oidc",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read"
},
};
This is application base, for assigning permissions per user, you can define roles in your scopes for the user and can then decorate your controller or methods with that Role for ex:
For admin: new Claim("role","Admin")
For guestuser: new Claim("role","guest")
[HttpGet]
[Authorize(Roles = "Admin")]
public IActionResult Edit()
{
//whatever
}
[Authorize(Roles = "Guest")]
[HttpGet]
public IActionResult View()
{
//whatever
}

asp.net web form client with identity server 4

I have a asp.net solution which consists of
1). asp.net identity server rc 3
2). asp.net Core web api
3). asp.net webform ( not in asp.net core, client)
I don't see any sample with identity server 4 and web form client. Can you please suggest how to authenticate web form user using identity server with asp.net identity and then call api with the access token ?
I don't see identity server 4 sample with web form client or sample
identity server 3 has a sample but it is doing everything in startup
When i see mvc client for identity server 4, it has all settings in configure method and then calls it like this
How will i apply Authorize attribute in webform so that i am redirected to identity server 4 for login and then after login when i call api like this:
how to change client for webform ?
new Client()
{
ClientId = "mvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("secret".Sha256())
},
RequireConsent = false;
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
StandardScopes.Roles.Name,
"API"
}
}
new InMemoryUser()
{
Subject = "1",
Username = "testuser",
Password = "password",
Claims = new List<Claim>()
{
new Claim("name", "Alice"),
new Claim("Website", "http://alice.com"),
new Claim(JwtClaimTypes.Role, "admin")
}
}
return new List<Scope>()
{
StandardScopes.OpenId, // subject id
StandardScopes.Profile, // first name, last name
StandardScopes.OfflineAccess,
StandardScopes.Roles,
new Scope()
{
Name = "API",
Description = "API desc",
Type = ScopeType.Resource,
Emphasize = true,
IncludeAllClaimsForUser = true,
Claims = new List<ScopeClaim>
{
new ScopeClaim(ClaimTypes.Name),
new ScopeClaim(ClaimTypes.Role)
}
}
};
public void CallApiUsingClientCredentials()
{
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "mvc", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:5001/identity");
var result = JArray.Parse(content).ToString();
}
[Authorize(Roles="admin)]
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
Late answer, but hopefully it helps someone, still supporting web forms.
There is no problem to use startup together with web forms. The only limitation is no place for AuthorizeAttribute there, but it's still not a problem, just put:
app.UseStageMarker(PipelineStage.Authenticate);
at the bottom of your
public void Configuration(IAppBuilder app)
method within OWIN Startup.An example Startup implementation could be fetched from my github. It works with MVC, Web Forms and additionally brings JWT validation from IdentityServer v.3' codebase, upgraded to compile with the latest OWIN libraries.
If I still left anything unclear, don't hesitate to ask in the comments.

401 response when using Xamarin and IdentityModel to WebAPI secured by IdentityServer 3

I am having an identical problem to this but their solution doesn't work for me.
I'm calling a WebAPI method (.Net 4.5.2) and the project has a reference to IdentityModel 1.13.1 and it is protected using IdentityServer 3 with the following code in the startup class -
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44305/core/",
RequiredScopes = new[] { "read", "write" },
// client credentials for the introspection endpoint
ClientId = "clientcredentials.client",
ClientSecret = "secret"
});
The clients configuration in the IdentityServer startup includes the following client definition -
new Client
{
ClientName = "Mobile Api Client",
Enabled = true,
ClientId = "clientcredentials.client",
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256()),
new Secret
{
Value = "[valid thumbprint]",
Type = "X509Thumbprint",
Description = "Client Certificate"
},
},
AllowedScopes = new List<string>
{
"read",
"write"
},
Claims = new List<Claim>
{
new Claim("location", "datacenter")
}
}
And in the Xamarin client (which also uses IdentityModel 1.13.1) ...
var token = IdentityServerClient.RequestClientToken(); // this returns a valid bearer token
TokenResultLabel.Text = token.Raw;
HttpClient apiClient = new HttpClient();
apiClient.SetBearerToken(token.AccessToken);
var result = await apiClient.GetStringAsync("[valid api URL]");
ApiResultLabel.Text = result;
I've tried it with IdentityModel 2.0 (latest compatible version), 1.13.1 (the version mentioned in the referenced question, and 1.9.2 (the version in the IdentityServer 3 samples)
Any help would be greatly appreciated
While the configuration allows the client to request both the read and write scopes, do you explicitly specify when you request the access token that you wish to get one containing these 2 scopes? This should happen in your IdentityServerClient.RequestClientToken method.
Allowing a client to request scopes doesn't mean these scopes will automatically be included in access tokens returned by IdentityServer.

Web app and web api authentication in same application

I have a web app MVC,using auth0 owin regular web app cookie based authentication.
This web app also has webapis which is used internally in the application. However i have a requirement to call this webapis from outside the application. So i created a restclient and tried to implement jwtbearerauthentication in application (but cookie based on authentication still in place).
Now when i call the webapi from other application it validates the bearer token gives no error however it redirects to login page due to cookie based authentication.
startup file:
public partial class Startup
{
private IPlatform platform;
public void ConfigureAuth(IAppBuilder app, IPlatform p, IContainer container)
{
platform = p;
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromDays(2),
SlidingExpiration = true
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var provider = new Auth0.Owin.Auth0AuthenticationProvider
{
OnReturnEndpoint = (context) =>
{
// xsrf validation
if (context.Request.Query["state"] != null && context.Request.Query["state"].Contains("xsrf="))
{
var state = HttpUtility.ParseQueryString(context.Request.Query["state"]);
AntiForgery.Validate(context.Request.Cookies["__RequestVerificationToken"], state["xsrf"]);
}
return System.Threading.Tasks.Task.FromResult(0);
},
OnAuthenticated = (context) =>
{
var identity = context.Identity;
//Add claims
var authenticationManager = container.Resolve<IAuthenticationManager>();
authenticationManager.AddClaims(identity);
if (context.Request.Query["state"] != null)
{
authenticationManager.AddReturnUrlInClaims(identity, context.Request.Query["state"]);
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
var issuer = "https://" + ConfigurationManager.AppSettings["auth0:Domain"] + "/";
var audience = ConfigurationManager.AppSettings["auth0:ClientId"];
var secret = TextEncodings.Base64.Encode(TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["auth0:ClientSecret"]));
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseAuth0Authentication(
clientId: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientId"),
clientSecret: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientSecret"),
domain: platform.ServerRole.GetConfigurationSettingValue("auth0:Domain"),
provider: provider);
}
}
webapiconfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Filters.Add(new AuthorizeAttribute());
ODataConfig.Setup(config);
var clientID = WebConfigurationManager.AppSettings["auth0:ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["auth0:ClientSecret"];
config.MessageHandlers.Add(new JsonWebTokenValidationHandler()
{
Audience = clientID,
SymmetricKey = clientSecret
});
}
Currently creating the jwt token from below code and posting using postman in header just to check if it works.. but redirects to login page.
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
I suspect what's happening is that your call to the API has a bearer token which fails validation (or there is no Authorize token at all), your API controller has an Authorize attribute, which, since there is no valid ClaimsPrincipal on the call throws 401. Auth0AuthenticationProvider picks that and assumes the call was to UI so redirects for user authentication. You may want to add an override in the Oauth0Provider to trap OnRedirectToIdP (or something like that), inspect the request and if it is to API, abot further handling and return Unauthorized.
Remove any [Authorize] from your API and see whether it works then. Also make sure your startup does not require Authorize for all controllers.
You may want to remove the authn part of your code (cookie and Oauth2Provider) and see whether you are getting to the API then.
A few years late i know, but i recently came across the same requirement in a project, and found this sample put together by a dev at Auth0.
https://github.com/auth0-samples/aspnet-core-mvc-plus-webapi
The example in the link allows for cookie authentication OR token authentication for the API endpoints.
The key takeaway for me was using attributes on your routes to tell the pipline what authentication mechanism to use. In my case i wanted cookie authentication for the UI and token authentication for the endpoints. i had no requirement to use both for any single area of the project.
controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
[Route("api")]
public string TestAuth()
{
return "All good " + this.User.FindFirst(ClaimTypes.NameIdentifier).Value + ". You only get this message if you are authenticated.";
}

Resources