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

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.

Related

Spring Security With Custom Login Flow

I am trying to add spring with spring security in an existing Struts based application. It is a Partial migration. All the new development should happen in Spring.
So what I want is,
User access a Secured URL
If the user is not authenticated Spring Should redirect to a specific URL (This will be a Struts URL)
After Old Struts Module does its work of authenticating the user, It saves an Object in HTTP Session which depicts the authenticated User.
Spring Should get this Object from Session and Authenticated the User.
Redirect the User to the requested Secured URL
I have tried to used formlogin().loginpage(<Struts_URL>). Also in the Struts code after authentication, I have updated the Spring's SecurityContext.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity pHttp) throws Exception
{
pHttp.antMatcher("/**").authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/<Struts_Login_Entry>.do");
}
...
#Override
public void configure(WebSecurity pWeb) throws Exception
{
// All the Strtus URL has .do suffix so excluding it
pWeb.ignoring().antMatchers("/*.do", "/portal/**");
}
}
After Authentication in Struts
...
MyUserDetails lUserDetails = new MyUserDetails(pUserName, pRole);
Authentication auth = new UsernamePasswordAuthenticationToken(lUserDetails, null, lUserDetails.getAuthorities());
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
...
The User is getting redirected to Struts page when he accesses any Spring URL But this happens even if the user is authenticated.
I am not so familiar with Spring Security, any suggestion or help?

Is it ok to add access_token authorities to the OAuth2LoginAuthenticationToken?

I have a simple spring boot application with two services - ui and resource.
I trying to configure oauth2+oidc authentication using uaa server.
When I login in the ui service, spring security creates authentication result (in OidcAuthorizationCodeAuthenticationProvider) using id_token and it doesn't contain any scopes except openid. When the authentication result is created it contains only one authority - ROLE_USER so a can't use authorization on the client side.
Is is ok to override OidcUserService and add to the user's authorities scopes from the access_token to check access on the client side?
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = super.loadUser(userRequest);
Collection<? extends GrantedAuthority> authorities = buildAuthorities(
user,
userRequest.getAccessToken().getScopes()
);
return new DefaultOidcUser(
authorities,
userRequest.getIdToken(),
user.getUserInfo()
);
}
Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/protected/**").hasAuthority("SCOPE_protected")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint().oidcUserService(oidcUserService())
.and()
...
It works but I'm not sure it's a good idea.
It is the approach as outlined in the Spring Security documentation, so the approach is fine.
The only thing is that when I have implemented it, I didn't add all the scopes to the authorities set - I pulled out the specific claim that had the role information - a custom groups claim that I configured in the identity provider's authorization server.
I include some example code for how to do this with Spring Webflux as most examples show how to do it with Spring MVC as per your code.
note: I'm very inexperienced with using reactor!
public class CustomClaimsOidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcReactiveOAuth2UserService service = new OidcReactiveOAuth2UserService();
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
log.debug("inside CustomClaimsOidcReactiveOAuth2UserService..");
Mono<OidcUser> mOidcUser = service.loadUser(userRequest);
return mOidcUser
.log()
.cast(DefaultOidcUser.class)
.map(DefaultOidcUser::getClaims)
.flatMapIterable(Map::entrySet)
.filter(entry -> entry.getKey().equals("groups"))
.flatMapIterable(roleEntry -> (JSONArray) roleEntry.getValue())
.map(roleString -> {
log.debug("roleString={}", roleString);
return new OidcUserAuthority((String) roleString, userRequest.getIdToken(), null);
})
.collect(Collectors.toSet())
.map(authorities -> {
log.debug("authorities={}", authorities);
return new DefaultOidcUser(authorities, userRequest.getIdToken());
});
}
}
...
#Bean
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService() {
return new CustomClaimsOidcReactiveOAuth2UserService();
}

unauthorizedRedirect set to false, still redirecting

I'm writing some REST api for my cake 3.0 application, and I need to set $this->Auth->unauthorizedRedirect to false, as the manual says that this would prevent my application to redirect to login url for unauthorized requests.
http://api.cakephp.org/3.0/class-Cake.Auth.BasicAuthenticate.html
The problem is that I'm trying to set it in my Users controller, and it doesn't work:
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['logout']);
// Change the authentication mode when using REST api
if(! $this->RequestHandler->accepts('html')) {
$this->Auth->unauthorizedRedirect = false;
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
This scripts works fine as detecting if a user is actually registered, but fails when I try to use wrong authentication data, showing the login form instead of throwing an error. What am I doing wrong?
Authentication and authorization are two different things
You are mixing up authentication and authorization, that's two different things. Logging in a user is authentication, testing whether a logged in user is allowed to access a specific action is authorization.
So the unauthorized redirect configuration applies to logged in users when accessing actions.
Handling unauthenticated requests
What you are looking for, ie throw an exception on unauthenticated requests, is done by the basic authentication adapter by default, so I assume that you actually aren't using this adapter!?
So if you are using a different adapter, this behavior is best implemented in either your controller where you are trying to identify the user
$user = $this->Auth->identify();
if (!$user) {
throw new ForbiddenException('Stop! Hammer time!');
} else {
$this->Auth->setUser($user);
}
or, in case you want the exception to be thrown for every controller, in a custom authentication adapters unauthorized() method, which is being invoked on unauthenticated requests before executing possible redirects. Quote from the docs:
Cookbook > Authentication > Handling Unauthenticated Requests
When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to login action. [...]
Here's a simple example that extends the form authentication handler:
src/Auth/MyCustomAuthenticate.php
namespace App\Auth;
use Cake\Auth\FormAuthenticate;
use Cake\Network\Exception\ForbiddenException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomAuthenticate extends FormAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
if(!$request->accepts('text/html')) {
throw new ForbiddenException('Ah ah ah! You didn\'t say the magic word!');
}
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustom'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects

How secure Rest-API by token

i use spring data rest 2.1 and would like secure my rest api, what is the best way to secure the api by token. I would have an process like the following steps. Is it also possible to change the base url from / to /api , but my controllers like 'home' would also display the jsp side from /home but not from /api/..
User go to login site
User login and get an token from server
User go to dashboard site and js do ajax call and set HTTP-Header Field 'X_AUTH_TOKEN' with token
You could configure base URI for you API using RepositoryRestConfiguration. Something like this:
#Configuration
public static class RepositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
try {
config.setBaseUri(new URI("/api"));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
You could add security using Spring-Security or if you like a simple custom solution use Spring Interceptor

Alternative to cookie based session/authentication

Is there an alternative to the session feature plugin in servicestack? In some scenarios I cannot use cookies to match the authorized session in my service implementation. Is there a possibility to resolve the session using a token in http header of the request? What is the preferred solution for that in case the browser is blocking cookies?
I'm using ServiceStack without the built-in auth and session providers.
I use a attribute as request filter to collect the user information (id and token), either from a cookie, request header or string parameter.
You can provide this information after the user takes login. You append a new cookie to the response and inject the id and token info on clientside when rendering the view, so you can use for http headers and query parameters for links.
public class AuthenticationAttribute : Attribute, IHasRequestFilter
{
public void RequestFilter(IHttpRequest request, IHttpResponse response, object dto)
{
var userAuth = new UserAuth { };
if(!string.IsNullOrWhiteSpace(request.GetCookieValue("auth"))
{
userAuth = (UserAuth)request.GetCookieValue("auth");
}
else if (!string.IsNullOrEmpty(request.Headers.Get("auth-key")) &&
!string.IsNullOrEmpty(request.Headers.Get("auth-id")))
{
userAuth.Id = request.Headers.Get("id");
userAuth.Token = request.Headers.Get("token");
}
authenticationService.Authenticate(userAuth.Id, userAuth.token);
}
public IHasRequestFilter Copy()
{
return new AuthenticationAttribute();
}
public int Priority { get { return -3; } } // negative are executed before global requests
}
If the user isn't authorized, i redirect him at this point.
My project supports SPA. If the user consumes the API with xmlhttprequests, the authentication stuff is done with headers. I inject that information on AngularJS when the page is loaded, and reuse it on all request (partial views, api consuming, etc). ServiceStack is powerful for this type of stuff, you can easily configure your AngularJS app and ServiceStack view engine to work side by side, validating every requests, globalizing your app, etc.
In case you don't have cookies and the requests aren't called by javascript, you can support the authentication without cookies if you always generate the links passing the id and token as query parameters, and pass them through hidden input on forms, for example.
#Guilherme Cardoso: In my current solution I am using a PreRequestFilters and the built-in session feature.
My workflow/workaround is the following:
When the user gets authorized I took the cookie and send it to the client by using an http header. Now the client can call services if the cookie is set in a http-header (Authorization) of the request.
To achieve this I redirect the faked authorization header to the cookie of the request using a PreRequestFilter. Now I am able to use the session feature. Feels like a hack but works for the moment ;-)
public class CookieRestoreFromAuthorizationHeaderPlugin : IPlugin
{
public void Register(IAppHost appHost)
{
appHost.PreRequestFilters.Add((req, res) =>
{
var cookieValue = req.GetCookieValue("ss-id");
if(!string.IsNullOrEmpty(cookieValue))
return;
var authorizationHeader = req.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.ToLower().StartsWith("basictoken "))
{
var cookie = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Split(' ').Last()));
req.Cookies.Add("ss-id",new Cookie("ss-id",cookie));
req.Items.Add("ss-id",cookie);
}
});
}
}