Microprofile JWT responding with 401 all the time - microprofile

I'm having an issue trying to get Microprofile JWT working for my REST resources.
I'm able to build a JWT so when decoded on jwt.ie it is
{
"kid": "Oh1YDQopers_qYMU4zQCmAf0UsFVD5D0NmkFE79s2q0",
"typ": "JWT",
"alg": "RS256"
}
{
"token_type": "Bearer",
"sub": "user12",
"upn": "user12",
"groups": [
"ADMIN",
"USER"
],
"jti": "a27582fc-21e2-4365-b485-ed7193606d8b",
"iss": "http://www.testissuer.com",
"exp": 1617226928,
"iat": 1617219728
}
My Application class is annotated with
#LoginConfig(authMethod = "MP-JWT")
#DeclareRoles({"USER", "SUPERUSER", "ADMIN"})
#ApplicationPath("/")
public class MyApplication extends Application {
Resource class is annotated with
#Path("/my")
#PermitAll
#RequestScoped
public class MyResource {
#Inject
#Claim(standard = Claims.groups)
private Set<String> groups;
#GET
#Produces(MediaType.TEXT_PLAIN)
#RolesAllowed("USER")
public String getString() {
if (groups != null) {
return "groups.size(): " + groups.size();
}
else {
return "groups is null";
}
}
}
The ear file which includes this war includes a META-INF/microprofile-config.properties file with entry:
mp.jwt.verify.issuer=http://www.testissuer.com
To test this I'm generating a fresh token and setting the Authorization header to the encoded JWT and calling GET /my which is returning a 401 response. Added Bearer before the encoded token makes no difference. The WWW-Authenticate header on the 401 response looks like it's looking for a Basic realm value.
If I take out the #RolesAllowed("USER") line then the response I get back is "groups is null" so it's like the injection is failing or cannot be mapped to the "groups" claim in the JWT.
Anyone run into this before?

#PermitAll annotation on resource class was wrong, using it prevents principal data from being injected. Fix was to use #DenyAll on class and #RolesAllowed or #PermitAll at method level.

Related

Flutter jsonwebtoken generate Appstore Connect API token invalid

I am trying to generate a Appstore Connect API token to manage my apps. The package I am using is dart_jsonwebtoken. The code:
final jwt = JWT({
"iss": "YOUR_ISSUER_ID",
"iat": DateTime.now().millisecondsSinceEpoch / 1000,
"exp": DateTime.now().add(Duration(days: 30)).millisecondsSinceEpoch / 1000,
"aud": "appstoreconnect-v1",
"scope": [
"GET /v1/apps?filter[platform]=IOS"
]
}, header: {
"alg": "ES256",
"kid": "YOUR PRIVATE KEY ID",
"typ": "JWT"
});
// Here I use the content from AuthKey_KEYID.p8 file
final token = jwt.sign(ECPrivateKey('''-----BEGIN PRIVATE KEY-----
YOUR AuthKey Content
-----END PRIVATE KEY-----'''), algorithm: JWTAlgorithm.ES256);
print('Signed token: $token\n');
The token is generated successfully, and I am using it to call the API, but I am getting an invalid token error:
{
"errors": [
{
"status": "401",
"code": "NOT_AUTHORIZED",
"title": "Authentication credentials are missing or invalid.",
"detail": "Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens"
}
]
}
I am pretty sure there is some wrong with my code to generate the token. Can somebody help me with that? Thanks in advance.
I got it working by using below ruby code. The generated token is working fine. I am trying to rewrite it using flutter.
require "base64"
require "jwt"
ISSUER_ID = "2ece1127-df4d-426a-a3eb-061e68f5eebe"
KEY_ID = "W9M2AM584Y"
private_key = OpenSSL::PKey.read(File.read('AuthKey_W9M2AM584Y.p8'))
token = JWT.encode(
{
iss: ISSUER_ID,
exp: Time.now.to_i + 20 * 60,
aud: "appstoreconnect-v1"
},
private_key,
"ES256",
header_fields={
kid: KEY_ID }
)
puts token

How to authenticate Jquery ajax requests with Keycloack,

I have a spring boot application (mvc) which is securing with keycloack. (using with spring-boot-starter-security and keycloak-spring-boot-starter)
I configured KeycloakWebSecurityConfigurerAdapter like that;
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
return this.tybsKimlikSaglayici;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().and().authorizeRequests().antMatchers("/",
"/home").permitAll().antMatchers("/admin").permitAll()
.anyRequest().authenticated().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/sso/logout")).permitAll();
http.exceptionHandling().accessDeniedPage("accessDeniedPage");
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Request to controller method which response html view works fine (keycloack authenticates request)
but,
Form action to controller method
Ajax request to rest controller method are not working (post, put, delete.. requests)
I added #CrossOrigin(origins = "*") to my controller.
Here is my ajax reqeust,
$.ajax({
type : "method_here",
contentType : "application/json; charset=utf-8;",
url : "url_here",
data : JSON.stringify(data),
timeout : 30000,
success : function(response) {
},
error : function(error) {
}
});
here is keycloack client
enter image description here
here is the kecloack json (i tryed application.properties file)
{
"realm": "demo-realm",
"auth-server-url": "url_of_sso_app",
"ssl-required": "external",
"resource": "kie-remote",
"principal-attribute": "preferred_username",
#"enable-cors": true, **tryed to add**
#"cors-max-age" : 10000,
#"cors-allowed-methods": "GET, POST, PUT, HEAD, OPTIONS",
#"cors-allowed-headers": "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headersl",
"credentials": {
"secret": "secret_of_realm_client"
}
}
how can I fix this issue. How can i authenticate ajax request help with keycloack.
i found my missing.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.POST, "/**")
.antMatchers(HttpMethod.PUT, "/**")
.antMatchers(HttpMethod.DELETE, "/**");
}
this method must be override in web security config. And it must be change like .antMatchers(HttpMethod.POST, "/allowed_method_path")
Edit:
This code ignoring authentication process for these http method types. Right solution does not use web.ignoring() method. Issue is related with csrf, (default spring security setting of csrf is enable) spring prevents put, delete, post http methods for to protect server from csrf attacks. If service does not consumes on browser, csrf can be disable, but service is producing a browser page, solution is that to configure csrf. Please check How to obtain csrf token in a velocity macro when using spring security
Thanks

The audience is invalid error

I have 3 projects 1- Javascript SPA 2- Web API Project, 3- IdentityServer with EF Core
I started debugging API and Identity Server and successfully get the jwt token but, when I try to get value from API method which has Authorize Attribute I get an error:
WWW-Authenticate →Bearer error="invalid_token", error_description="The audience is invalid"
I could not found any property about audience in auth options. This is my configuration in API project
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
ApiSecret="secret",
Authority = "http://localhost:5000",
ApiName="fso.Api",
RequireHttpsMetadata = false,
});
And my Config.cs file in Identity
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource()
{
Name = "fso.Api",
DisplayName = "feasion API",
Scopes =
{
new Scope("api1"),
new Scope(StandardScopes.OfflineAccess)
},
UserClaims =
{
JwtClaimTypes.Subject,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.FamilyName,
JwtClaimTypes.PhoneNumber,
JwtClaimTypes.PhoneNumberVerified,
JwtClaimTypes.PreferredUserName,
JwtClaimTypes.Profile,
JwtClaimTypes.Picture,
JwtClaimTypes.Locale,
JwtClaimTypes.IdentityProvider,
JwtClaimTypes.BirthDate,
JwtClaimTypes.AuthenticationTime
}
}
};
}
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "fso.api",
AllowOfflineAccess=true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes =
{
StandardScopes.OfflineAccess,
"api1"
}
}
};
}
}
See here for what this claim is about:
The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected....
So your API's name must exist in the aud claim for the JWT to be valid when it is validated by the middleware in your API. You can use jwt.io to look at your token by the way, that can be useful to help make sense of it.
In order to have IdentityServer to add your API's name to the aud claim your client code (which is attempting to get a resource from the API and therefore needs an access token) should request a scope from your API. For example like this (from an MVC client):
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "api1" },
//other properties removed...
});
To avoid the error, audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope.
In API application as ApiName
In IdentityServer Clients configuration as AllowedScope
In API Resources configuration as ApiResource
See details ( previously available in IdentityServer4 wiki):
When configuring a new API connection in identityServer4, you can get an error:
WWW-Authenticate: Bearer error="invalid_token",
error_description="The audience is invalid"
To avoid the error, Audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope :
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "openid", "profile", "offline_access", "MyApi" },
//other properties removed for brevity...
});
In API application as ApiName
//Microsoft.AspNetCore.Builder.IdentityServerAuthenticationOptions
var identityServerAuthenticationOptions = new IdentityServerAuthenticationOptions()
{
Authority = Configuration["Authentication:IdentityServer:Authority"],
RequireHttpsMetadata = false,
EnableCaching = false,
ApiName = "MyApi",
ApiSecret = "MyApiSecret"
};
In IdentityServer \IdentityServerHost\Configuration\Clients.cs
(or corresponding Clients entry in the database)
var client = new Client
{
ClientId = clientId,
//other properties removed for brevity...
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
//IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess, "MyApi",
},
};
In IdentityServer \IdentityServerHost\Configuration\Resources.cs (or corresponding ApiResource entry in the database) as apiResource.Scopes
var apiResource = new ApiResource
{
Name = "MyApi",
ApiSecrets =
{
new Secret("MyApiSecret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Profile,
},
};
In your app configuration file in AD configuration section add "Audience" line:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<-- Enter the Client Id -->",
"Audience": "<-- Enter the Client Id -->",
"TenantId": "<-- Enter the tenantId here -->"
}
In my case "ClientId" & "Audience" was the same.
P.S.: And if after that you'll see
IDW10201: Neither scope or roles claim was found in the bearer token
Add another line to AD configuration:
"AllowWebApiToBeAuthorizedByACL": true
More here
In IdentityServer had to add claim "aud" to the jwt Token. In Order to do that under .AddJwtBearer("Bearer", options => options.Audience="invoice" and set ApiResource
Reference Link https://identityserver4.readthedocs.io/en/latest/topics/resources.html#refresources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoice", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
}
};
}

How to validate a token issued by IdentityServer3 using WebApi

I need help to understand what is happening in the following scenario.
I have an architecture drawn as below.
0 - Presentation
1 - Authentication Server (Using IdentityServer3)
2 - Isolated WebApi using OData (Without Default Controllers by AspNet.Identity)
3 - Business Services
4 - Custom Repository
5 - Repository generic and data access layer
The layers of number 0 and 2 necessarily perform their authentication through layer number 1.
Authentication is working perfectly for my number 1 presentation layer using implicit flow.
However, the WebApi number 2 small services layer is failing to authenticate by passing the token provided by the authentication server.
I received the following message.
{
"odata.error": {
"code": "",
"message": {
"lang": "en-US",
"value": "Authorization has been denied for this request."
}
}
}
My ConfigAuth in WebApi
public static void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
ClientId = "WebApi",
ClientSecret = "secret",
Authority = "http://localhost:35373/identity", // TODO: Vai mudar com a versão final
RequiredScopes = new[] { "read" , "write" , "offline_access" }
});
app.UseWebApi(WebApiConfig.Register());
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
This is my request token from identityserver3
{
"access_token": "5f4ca4bc0514158409903080af5c1081",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "a8cd624dd3169a607bdb24ad0833d1b3"
}

Issues with GetProfileDataAsync after upgrade to IdentityServer3 v2.5 from v1.6.3

We've been running IdentityServer3 v1.x successfully over the past year, but have now upgraded to v2.5 from v1.6.3.
We have a custom UserService that implements the IUserService, so this was modified for the new context parameters and we are able to login, but are having issues with the GetProfileDataAsync
The UserService that was built for v1.6.3 works fine and we can see 12 requested claim types in requestedClaimTypes
public Task<IEnumerable<Claim>> GetProfileDataAsync(ClaimsPrincipal subject,
IEnumerable<string> requestedClaimTypes = null)
{
var userClaims = claimsService.GetByUserIdAsync(int.Parse(subject.GetSubjectId()));
var claims =
userClaims.Where(x => requestedClaimTypes != null && requestedClaimTypes.Contains(x.Type));
return Task.FromResult(claims);
}
But since upgrading to v2.5, the only requested claim type is sub in context.RequestedClaimTypes, rather than 12 we used to get. The only way to get all 12 in is to change the AlwaysIncludeInIdToken to true
Our updated UserService for v2.5 is
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
if (context == null) throw new ArgumentNullException("context");
var subject = context.Subject;
var requestedClaimTypes = context.RequestedClaimTypes;
var userClaims = await _claimsService.GetByUserIdAsync(int.Parse(subject.GetSubjectId()));
if (userClaims != null)
{
var claims = userClaims.Where(x => requestedClaimTypes != null && requestedClaimTypes.Contains(x.Type));
context.IssuedClaims = claims;
}
}
We use SQL to store our Clients and Scopes but we've not changed any data, other than to use the IdentityServer3.EntityFramework provider
Our logging shows that the 4 scopes are being requested which have their associated scope claims as before
Info: Authorize request validation success {
"ClientId": "MyApp",
"ClientName": "MyApp",
"RedirectUri": "https://xxx:44300/",
"AllowedRedirectUris": [
"https://xxx:44300/"
],
"SubjectId": "9",
"ResponseType": "code id_token",
"ResponseMode": "form_post",
"Flow": "Hybrid",
"RequestedScopes": "openid profile roles user",
"State": "OpenIdConnect.AuthenticationProperties=xxxx",
"Nonce": "xxx",
"SessionId": "xxx",
"Raw": {
"client_id": "MyApp",
"redirect_uri": "https://xxx:44300/",
"response_mode": "form_post",
"response_type": "code id_token",
"scope": "openid profile roles user",
"state": "OpenIdConnect.AuthenticationProperties=xxx",
"nonce": "xxx"
}
}
What do we need to do to get it to request all the claim types as before??
The spec says that if an access token is requested, the id_token should only contain the minimal user-related claims (aka sub). The access token can then be used to retrieve the other claims from the userinfo endpoint.
This is an optimization mechanism to keep the id_token as small as possible.
We had a bug where this was done for id_token token but not for code id_token (which is what you are using). This bug was fixed at some point along the way. I guess that is the behavioural change you are seeing.
Either set the AlwaysIncludeInIdToken property on the scope claims you want to be included - or use the userinfo endpoint to retrieve the claims.