Getting Insufficient scope for this resource using Spring Boot and OAuth2 - api

I'm trying to run a small proof of concept for a REST API using Spring Boot 1.2.2 and secured with OAuth2. In order to do that I created two projects, one for the authentication server and another one for the REST service.
When running the projects, everything goes right. I get a token from the authentication server and then using that token, I managed to call the service and get the expected result.
The issue happens when I try to validate the scopes in the service. As soon as I add the line:
#PreAuthorize("#oauth2.hasScope('webshop')")
to my service, in the code:
#RestController
public class ProductService {
#RequestMapping("/product/{productId}")
#PreAuthorize("#oauth2.hasScope('webshop')")
public Product getProduct(#PathVariable int productId) {
return new Product(productId, "name", 123);
}
}
I get this response:
{
"error": "insufficient_scope",
"error_description": "Insufficient scope for this resource",
"scope": "webshop"
}
The authentication server is configured as:
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials")
.scopes("webshop");
}
}
This is the actual call to get the token:
juan#ubuntu:~/ms/core/product-service$ curl -s acme:acmesecret#localhost:9999/uaa/oauth/token -d grant_type=client_credentials -d scope=webshop
{
"access_token": "ac3a9768-4ebb-44e3-a49f-21e2f117d0b4",
"token_type": "bearer",
"expires_in": 43199,
"scope": "webshop"
}
And then, to call the service:
juan#ubuntu:~/ms/core/product-service$ TOKEN=ac3a9768-4ebb-44e3-a49f-21e2f117d0b4
juan#ubuntu:~/ms/core/product-service$ curl 'http://localhost:8080/product/32' -H "Authorization: Bearer $TOKEN" -s
{
"error": "insufficient_scope",
"error_description": "Insufficient scope for this resource",
"scope": "webshop"
}
I feel that I am just missing something simple because of lack of a concept. If the client has been created in memory and is supposed to be allowed to do "webshop" and the token has been generated successfully, why is the error generated?
Thanks in advance for pointing to some direction or help.

This is caused by the UserInfoTokenServices class failing to extract the scopes from the request and therefore not adding them to a new OAuth2Request.
Specifically this method
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = this.authoritiesExtractor
.extractAuthorities(map);
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
neglects to extract the scopes map from the oauth2Request map entry, and then passes a null for the scopes collection into the constructor for the Oauth2Request scope parameter.
This means that even though you might have authorised your user to a particular scope, when the authserver is queried via the security.oauth2.resource.userInfoUri endpoint,the authorised scopes are ignored in the calling code, so the PreAuthorize check fails.
This looks like a bug to me; I've raised an issue on github.

Related

How to trigger Quarkus OIDC-Client trigger on Header Authorization: Basic

I'm working to convert a Spring application to Quarkus. I want both Bearer and Basic Authentication to work. This will also use Keycloak Authorization Service (main reason I'm moving to Quarkus).
The quarkus.oidc configuration works great and I have no problem with the Bearer Token Authorization.
The quarkus.oidc-client configuration does contain grant-type password option that I assume I could use to convert a Authorization: Basic Header to username/password and generate a Bearer Token. This is the spot I think I'm stuck.
Do I need to extend HttpAuthenticationMechanism? The extended class would invoke the oidc-client with the client credentials, grant-type=password and username/password from the Authorization: Basic header?
application.properties
quarkus.http.auth
quarkus.http.auth.basic=true
quarkus.http.auth.permission.basic-bearer.paths=/api/config/*
quarkus.http.auth.permission.basic-bearer.policy=authenticated
#end quarkus.http.auth
#quarkus.oidc
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc.discovery-enabled=true
quarkus.oidc.client-id=${keycloakResource}
quarkus.oidc.credentials.secret=${keycloakCredentialsSecret}
#end quarkus.oidc
#quarkus.oidc-client
quarkus.oidc-client.enabled=true
quarkus.oidc-client.auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc-client.discovery-enabled=true
quarkus.oidc-client.client-id=${keycloakResource}
quarkus.oidc-client.credentials.secret=${keycloakCredentialsSecret}
quarkus.oidc-client.grant.type=password
Do I need to add SecurityIdentity below?
#Path("/api/config")
public class ConfigController {
#Inject
Tokens tokens;
String success;
public String getSuccess() {
return success;
}
#GET
#Path("")
#Produces(MediaType.APPLICATION_JSON)
public Response sendOk() throws JsonProcessingException {
success = tokens.getAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
return Response.ok(objectMapper.writeValueAsString(this)).build();
}
#GET
#Path("/owners")
#Produces(MediaType.APPLICATION_JSON)
public Response sendOwners() throws JsonProcessingException {
success = tokens.getAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
return Response.ok(objectMapper.writeValueAsString(this)).build();
}
}
I was trying to get the quarkus.oidc-client to manage Basic Authorization header without need to add my own HttpAuthenticationMechanism

Keycloak Spring UMA denied

I am trying to implement authorization for this use case:
user can access only his own resources
admin can access everything
I am trying out Keycloak and it's resource server. For testing purposes and to understand these scopes and permissions and stuff, I have created test client weather-api and one resource Weather with url /weatherforecast.
Then I have scope weather:read and policy that every user with role weatherer can read that resource.
Now when I try to evaluate on a user with that role, I get PERMIT:
and another user without this role gets DENY.
so I guess my policies and permissions are set correctly.
When I try to use this from my service with user-managed-access disabled, I get permit too.
But when I enable user-managed-access, it fails.
I see in debug log that it gets permissions token:
{
...
"permissions": [
{
"scopes": [
"weather:read"
],
"rsid": "ae5ac493-b7dc-481e-9204-a664d1558a51"
}
],
...
}
but then the next message is
Policy enforcement result for path [http://192.168.0.9:5001/weatherforecast] is : DENIED
I tried to debug Keycloak library and found something I don't really understand.
In KeycloakAdapterPolicyEnforcer this part of code:
#Override
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
AccessToken original = accessToken;
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
return true;
}
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade, claims);
if (accessToken == null) {
return false;
}
...
}
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
return null;
}
...
}
so when UserManagedAccess is not null, requestAuthorizationToken returns null and then it acts like the user is unauthorized with HTTP 401.
What am I missing here? Why it works only without UMA?
I have looked at these (app-authz-uma-photoz, devconf2019-authz) examples and haven't noticed what I am missing.
Except they are actually creating some resources for users from the Java app, I'm not. But I guess it shouldn't matter if I'm protecting user created resources or single "pre-made" URL, right? It should depend only on correct permissions and since they evaluate to PERMIT so I don't see why this doesn't work.
And one more question. Isn't this UMA thing overkill for just "user can access his own, admin can access everything" case when there will never be any sharing between users? I was thinking about some simpler way that could work without creating user resources in Keycloak but I couldn't think of anything, I believe I still need to have connected user ID with some resource ID to make this working.

I am receiving status 401 Unauthorized when I attempt to call my secured API by keycloak

I have an web application providing an REST API endpoints, secured with spring security and SSO Keycloak.
My web application works fine with protection on and I can access REST API endpoints using web browser, after authentication on keycloak and redirect back to endpoint. I am getting expected JSON response, which mean user and role should be configured correctly.
But when i want to call a provided REST API endpoint from another web application or using Postman, i have every time an error 401 error: unauthorized.
In the header of the request I am putting:
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgO...." token.
I am able to fetch this token from keycloak in consumer web application and also when I am making a request using postman.
My Keycloak configuration in my spring security config:
#KeycloakConfiguration
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/api/v1/**").hasRole("USER")
.antMatchers("/admin/**", "/app/**").hasRole("ADMIN")
.anyRequest().permitAll();
}
}
My keycloak.json file:
{
"realm": "realm-name",
"auth-server-url": "https://auth.server.com/auth/",
"ssl-required": "external",
"resource": "resource-name",
"verify-token-audience": true,
"credentials": {
"secret": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"use-resource-role-mappings": true,
"confidential-port": 0
}
My method in the controller of the api:
#RestController
#RequestMapping("/api/v1")
public class WakeMeUpController {
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
public String testMeUp() {
return "I am tested!";
}
}
This is a part of my client configuration in keycloak:
keycloak Client screen
Rest keycloak config is how it is coming by default.
Thx for your help!
Thank you. The problem was into audience configuration in keycloak.
For a client, you need to configure new Mapping under Client -> <client_name> -> Mappers and add another Mapping with mapper type 'Audience' and appropriate 'Included Client audience' (i choose client id i have used to make request)
Also another problem that i have faced after i solve this one, was that user was authenticated but was forbidden the access to endpoint.
Explicitly adding role into client configuration itself and assigning this role to the user solve it and it is working.
From huge help was setting spring logging to DEBUG
logging.level.org.springframework.security=DEBUG
And also XXX-Authentication header in server response that gives better description what the issue is.

WebAPI 2 Create Custom Authentication Token

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.

Google+ api - Key from https://accounts.google.com/o/oauth2/token gives 401 errors

I'm having some trouble with Google+ API OAuth2 tokens.
Here is a simple program that gets an OAuth2 access token:
HttpClient httpclient = new HttpClient();
PostMethod postMethod = new PostMethod("https://accounts.google.com/o/oauth2/token");
NameValuePair[] data = {
new NameValuePair("client_id", "API KEY HERE"),
new NameValuePair("redirect_uri", "URL HERE"),
new NameValuePair("client_secret", "SECRET HERE"),
new NameValuePair("code", "CODE HERE"),
new NameValuePair("grant_type", "authorization_code")
};
postMethod.setRequestBody(data);
try {
int result = httpclient.executeMethod(postMethod);
assertEquals(result, 200);
System.out.println("Response body: ");
System.out.println(postMethod.getResponseBodyAsString());
} catch (IOException e) {
e.printStackTrace();
}
This successfully generates an OAuth2 token. Such as: ya29.AHES6ZTZgptKHyZ530MoYVDPaeXvjK5DWQzPqxoNNEL2C7gsQwGfmvfT8Q
Then I set up a simple test program that can test calling the /people API from that token:
#Test
public void testGoogleAPIAuthdRequest() throws Exception {
String feedUrl = "https://www.googleapis.com/plus/v1/people/me";
String apiKey = "MY API KEY";
String oauthCode = "OAUTH CODE FROM ABOVE";
String jsonStr = executeGoogleFeed(feedUrl, apiKey, oauthCode).getResponseBodyAsString("UTF-8");
}
public Response executeGoogleFeed(String feedURL, String apiKey, String oauthCode) throws Exception {
StringBuilder urlStr = new StringBuilder();
urlStr.append(feedURL);
urlStr.append("?key=");
urlStr.append(apiKey);
Map<String, String> hashMap = new HashMap<String, String>();
hashMap.put("Authorization", "Bearer " + oauthCode);
return HttpUtil.doHttpRequest(urlStr.toString(), MethodType.GET.toString(), null,
hashMap);
}
This gives me a 401 error. Permission denied.
When I go to https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=INSERT_ACCESS_TOKEN, the Token shows as valid.
Also, when I go to https://developers.google.com/+/api/latest/people/get and get the OAuth token from the OAuth2 sign-on feature... and I plug that OAuth token into my JUnit test... it works!
Anyone know why my call to https://accounts.google.com/o/oauth2/token cannot be used as the Bearer parameter in Google+'s api?
It sounds like the underlying problem is that you're not requesting the plus.me scope when you send the user to authenticate themselves and authorize your app. This is done in the step before what you've shown here, and is the component that returns the OAuth2 "code" that you're adding above. You may want to update your example to illustrate how you're getting the code and what scopes you're using to do this.
If you are setting the scope correctly, and you're using the code that is returned, it could be that you keep re-using the same code. The OAuth2 code returned from the first stage can only be used once, and must be used very quickly after it is issued. It is exchanged for an access_token which has a limited lifetime (which is what you're correctly trying to do), and a refresh_token which has an unlimited lifetime and is used to generate fresh access_tokens.
See https://developers.google.com/accounts/docs/OAuth2WebServer for full details about the multi-step process used to get, and then exchange, the OAuth2 code.