Spring OAuth2 resource server with Google Authorization server - spring-security

I am trying to implement a simple Spring OAuth2 resource server using google as OAuth server.
Basically, I've been following guides like this one spring-oauth2-with-google
application.yml:
spring:
security:
oauth2:
client:
registration:
google:
client-id: *******.apps.googleusercontent.com
client-secret:********_
scope:
- email
- profile
- openid
resourceserver:
jwt:
issuer-uri: https://accounts.google.com
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
SecurityConfig.java:
#Configuration
public class SecurityConfig {
#Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
;
return http.build();
}
}
UserController.java
#RestController
#RequestMapping("/user")
#RequiredArgsConstructor
public class UserController {
#GetMapping("/{id}")
public void getUser(#PathVariable String id) {
System.out.println("Id: " + id);
}
}
I am able to get the google JWT through postman as described in the guide, but no matter how hard I try, when I try to consume my end point, through postman, the response is always 401. I have already tried to set a space between the Bearer keyword and token_id.
Error in postman:
Bearer error="invalid_token", error_description="Invalid token", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
but if i review the token in google token info the result seems ok:
"issued_to": "263667859573-jve8vplquh7qn4ft7aaj1t1m9boaq5d6.apps.googleusercontent.com",
"audience": "263667859573-jve8vplquh7qn4ft7aaj1t1m9boaq5d6.apps.googleusercontent.com",
"user_id": "112897290372529438679",
"scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
"expires_in": 3296,
"email": "javier#email.com",
"verified_email": true,
"access_type": "online"

Maybe, the access-token you get from Google is just not a JWT but an opaque token, in which case configuring your resource-server with a JWT decoder won't work. try to "open" one of your access-tokens in a tool like https://jwt.io to be sure.
I couldn't find a standard introspection endpoint for such Google opaque tokens, just https://www.googleapis.com/oauth2/v3/tokeninfo to which you can submit access_token as request param. If token is valid, you'll get claims in response. So, you could actually configure your resource-server with token introspection (instead of JWT decoder), providing your own token introspector to call the tokeninfo endpoint, check the response and either throw an exception or return an Authentication of your choice.
http.oauth2ResourceServer().opaqueToken().introspector(...);
But this is quite some work and introspection is far less efficient than decoding JWTs (first requires to submit token to Google for each and every incoming request on your resource server, when fetching public key only once for all request is enough for second).
It is likely that Google just doesn't want us to use their authorization-servers for our own resource-server except for a few limited cases (and I'm not sure which ones).
An alternative for you is using another authorization-server capable of federating "social" identities (Google, of course, but also Facebook, Github, etc.). Plenty do so: Keycloak is a famous on premise solution, but if you don't want to bother at maintaining an authorization-server yourself, you could have a look at SaaS like Auth0 (which has a free tier). Those two are just my current preferred in a galaxy of solutions, just search for "OIDC authorization-server" and choose your own.

Related

IBM API Connect - Cannot pass the meaningful message from response of Authentication URL to oAuth/token Client

I have an oAuth Provider which uses Authentication URL. In some cases, my Authentication URL returns "non-200" with meaning messages e.g. incorrect username, incorrect password, user locked etc.
I wonder how to pass the meaningful message from Authentication URL to the client who is requesting the token via API (oauth2/token)??
I have tried many ways, the API Connect always results 401 - invalid grant only.
Here is my oAuth Provider Code
x-ibm-configuration:
testable: true
enforced: true
phase: realized
oauth2:
client-type: confidential
scopes:
weather: Weather Information
openid: Enable OIDC
grants:
- password
identity-extraction:
type: basic
authentication:
x-ibm-authentication-url:
url: 'https://8hxovobj7g.execute-api.eu-west-2.amazonaws.com/Prod/auth'
authorization:
type: authenticated
access-token:
ttl: 1500
refresh-token:
count: 2048
ttl: 2682000
gateway: datapower-gateway
assembly:
execute:
...
...
when authen fail due to incorrect username, the authentication URL return
401 Unauthorized
{
"error": "incorrect username"
}
However, when I use postman to post a request to API Connect Gatwatway to the oAuth Provider (path: /oauth2/token)
if correct username password (authen URL user registry return 200-OK, I will get
{
"token_type": "bearer",
"access_token": "AAIkNTU4M2RlMzktODY1NS00ZDQ1LTgyMjctODEyMDM4MDUzMTE2m7lBYXfx73OVPONAHoLT5VNdtSVD40Hu-M3nAQPu6wdviOxcIfbsOXBwt-Iy8EAgLzuATlZB7RBME_U5Ymd5fDkRTwy05G9zGmV7mIkawaELtiOj4xdzQr7Vn-indlv-y1NFEjvRv2VrK0d3TOqZnTEj5heDdY7Q0X9BFeydV4MtS-gCpnj-9l6TU3XqyeiK5hGnBZkZRAWOIskLm4KCyf8n_mnsi42vN9GLxlxoO9EmuHAwXOxr_aocKaaVlLKK5vDMHBRws2Vguqk3eVuoh9EnkRZvjbTurmW57bCgX3nMTd6MwcEYFkAGh-cOcEDyydZR6BI_pLuwaUM9RN8Vnb7EATQjzW2d_eHKQyjShcyM0TqxzhYq3q90fLfJLo08WxDgTFaKpGHA6qoZmUpYRLeyyImhOPtyd9p1l9z87g52duHbL1cyVGErHktTVpeXsmIRtn-QTTvI4jWmjxPZnSYj_rEeR9S8QAxYpHSEPmJQQmsjISf2SIRLABwuhG9dKyrrzs3UTotVyIotxmJjc9lfEsEtDTz9Ej--yQFw97ESHCVEvOkifeyIJ9F5MyPFh7fMEoGGwyDmWEfZSYRpkLg4_ib3dbjkGAuthiwjdA0",
"metadata":
...
}
if incorrect username/ password (authen URL user registry return 401-Unauthorized with response-body (error message), I will get
401-Unauthorized
{
"error": "invalid_grant"
}
The Authentication URL is custom made. I have tried to make different returns in authentication URL (different http response code/ body/ headers) which all cannot affect the end-user's oauth's oauth/token result which always return the msg "invalid grant". what I really want to pass the authentication URL result message to the end-users to let the users know what is wrong such as incorrect username
Thanks for your kindly help
Unfortunately, it seems that in APIC v5 it is not possible to adjust OAuth error based on the response from Authentication URL call.
You can check DataPower XSLT code which handles this case (local:/isp/aaa-ldap-lib.xsl) to see more details. For any non-200 response code returned by an authentication URL, the same hardcoded error is produced. Only a response code is checked and all response information is discarded after that check - including a response code.
In the case of non-200 response code returned by authentication URL following XSLT code is executed:
<xsl:call-template name="error">
<xsl:with-param name="code" select="'401'"/>
<xsl:with-param name="reason" select="'Unauthorized'"/>
<xsl:with-param name="challenge" select="'Basic'" />
</xsl:call-template>
That means it is not possible to distinguish between errors caused by different authentication URL responses in OAuth API assembly catch. You can catch UnauthorizedError in the created assembly in OAuth API but you would have no additional information based on which you could create custom error response.
However, if you are using on-premise APIC v5 and you have access to the DataPower Gateway you could:
change mentioned xsl file to propagate status code/reason from authentication URL (possibly using gateway extensions)
info necessary, add a custom assembly to your OAuth API where you would catch UnauthorizedError and add GatewayScript code to adjust behavior
GatewayScript code to adjust behavior in case of UnauthorizedError caught would be something like:
let p = session.name('policy');
let e = p.getVariable('fw/exception');
let statusCode = e.httpCode;
let statusReason = e.httpReasonPhrase;
...

Getting Bearer token in OAuth2.0 using Rest Assured

For my API testing, I have been getting the Authentication Token with the help of Postman. API uses OAuth2.0 for authentication.
While using Postman, I am entering the details like Token Name, Grant Type, Access Token Url, Client ID, Client Secret, Scope, Client Authentication. Once clicked on the Request Token, Bearer token is received and is updated in the Header of the request. Please refer the image attached.Getting OAuth2.0 bearer token with Postman
But now, I want to do it with Rest Assured using Java. Hence I want to know the code how do I get the Bearer token by using Rest Assured?
Assuming you are sending the client id secret and other details in the Query Parameters, for the GET and your response header access_token has the value you are looking.
public class Sample {
String oauth_token ;
#BeforeTest
public void sampletest() {
oauth_token = RestAssured.given().auth().basic("username", "password").
.queryParams("client_id", "client_id_value")
.queryParams("client_secret", "client_secret_value")
.queryParams("grant_type", "authorization_code")
.queryParams("redirect_uri", "https://someuritoredirect")
.queryParams("otherparams", "sampleparam")
.when().get("https://uri.to.get.hostname.com/oauth2/access_token")
.then()
.statusCode(200)
.extract().header("access_token");
}
}

Feign Oauth how to manually set the bearer token value?

I'm developping a Rest API, MyApi.
In there, I'm using Feign (and swagger codegen) to generate a client for another API, let's call it Ext-API.
The user will have called Ext-API previously and among other things will have retrieved a JWT Token.
He'll then call my API using Basic Auth and in the body it'll give me the JWT token.
I'm to use this JWT token as auth header to connect to Ext-API from my API and do some more stuff on behalf of the user.
However all example of Oauth & Feign example rely on the Oauth also being used to connect to the API using the generated client.
I can't find how I could, on every request, update the Oauth Token.
There are nothing exposed by feign to do this.
Atm I'm using regular Rest template.
You can used #RequestHeader in feign Client, see below
#FeignClient(name = "<name>", configuration = <configclass>)
public interface Client {
public final String AUTH_TOKEN = "Authorization";
#RequestMapping(method = RequestMethod.GET, value = "users", produces = "application/json")
public HttpEntity<List<User>> getUsers(#RequestHeader(AUTH_TOKEN) String oruToken,
#RequestParam("<param>") Integer value);
}
and from you program you can pass token to feign client

How to call JHipster (Spring) OAuth2 Rest server using Postman Authentication helpers

Postman has Authentication helpers to help with authenticated calls and I'm trying to use the OAuth 2.0 helper to call a REST server created by JHipster using Spring (Security, Social, etc).
I've tried a lot of configurations, this is the screen (client ID and Secret were masked):
For the Authorization URL I've tried:
http://127.0.0.1:8080/oauth/authorize
http://127.0.0.1:8080/#/login (the app's login route)
The closer I get from receiving a token back to Postman is:
I don't know why it's erring like this. Maybe I'm setting the Callback URL incorrectly? Do I need to do this in the server or in the client (AngularJS)?
Does anyone have any idea of what's wrong? I appreciate your help.
JHipster is currently setup to use the "password" oauth2 grant type. The helper oauth2 helper only seems to work with "authorization code" and "client credentials" grant types.
What you'll want to do is first call your app's token endpoint directly as the angular app does in
src/main/webapp/scripts/components/auth/provider/auth.oauth2.service.js
POST http://localhost:8080/oauth/token?username=MY_USERNAME&password=MY_PASSWORD&grant_type=password&scope=read%20write
where your username and password can be "user" and "user" respectively, for example and with one header set:
Authorization: Basic AAAAAA
where AAAAAA is your (clientId + ":" + clientSecret)--all base64-encoded. You can use https://www.base64encode.org/. For example if your clientId is "jhipsterapp" and your clientSecret is "mySecretOAuthSecret", replace AAAAAA with "amhpcHN0ZXJhcHA6bXlTZWNyZXRPQXV0aFNlY3JldA==" since that is "jhipsterapp:mySecretOAuthSecret" base64-encoded.
That should return you an access_token. Now hit your API endpoints by calling them with the access_token from your password request in your header like this.
Authorization: Bearer access_token_from_earlier_token_request
Update: if you're using microservices and UAA, then see Niel's answer https://stackoverflow.com/a/45549789/1098564
To build on #sdoxsee's answer:
Currently (August 2017) JHipster generates a class called UaaConfiguration with the configure(ClientDetailsServiceConfigurer) method setting up the client ID, client secret, scope and grant type. Refer to these settings (including the referenced JHipster properties in the application*.yml) to populate the Postman authentication helper, using /oauth/token as both Auth URL and Access Token URL.
Example:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/*
For a better client design, this should be done by a ClientDetailsService (similar to UserDetailsService).
*/
clients.inMemory()
.withClient("web_app")
.scopes("openid")
.autoApprove(true)
.authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
.and()
.withClient(jHipsterProperties.getSecurity().getClientAuthorization().getClientId())
.secret(jHipsterProperties.getSecurity().getClientAuthorization().getClientSecret())
.scopes("web-app")
.autoApprove(true)
.authorizedGrantTypes("client_credentials");
}
And,
jhipster:
security:
client-authorization:
client-id: internal
client-secret: internal
Means your authentication helper should be populated as follows:

Request new access token using refresh token in username-password grant in DotNetOpenAuth

I'm trying to implement a OAuth2 Authorization Server using DotNetOpenAuth. The client is JavaScript based thus incapable of holding any secrets. This is exactly the same problem like this question but with another framework.
The client requests (against the token endpoint) access_token and refresh_token with following parameters:
grant_type: password
username: foo
password: bar
This does work. Now I want use the refresh_token and make a request against the token endpoint with the following parameters:
grant_type: refresh_token
refresh_token: ABCDEF
This gives me the following response:
{"error":"invalid_client","error_description":"The client secret was incorrect."}
Which does make (at least some) sense because RFC6749 states that:
Because refresh tokens are typically long-lasting credentials used to
request additional access tokens, the refresh token is bound to the
client to which it was issued. If the client type is confidential or
the client was issued client credentials (or assigned other
authentication requirements), the client MUST authenticate with the
authorization server as described in Section 3.2.1.
If I change my request like so:
grant_type: refresh_token
refresh_token: ABCDEF
client_id: MYCLIENT
client_secret: CLIENT_SECRET
The problem is my client is not supposed to be confidential (because it is client side JavaScript after all).
This is how the client is defined:
New ClientDescription(ApiKey, New Uri(allowedCallback), ClientType.Public)
I searched through the DotNetOpenAuth source code and found no use of the ClientType. To me it looks like it is not used at all.
It is also not possible to the set an empty client secret, because the DotNetOpenAuth source code actively checkes against this (ClientAuthenticationModules.cs):
if (!string.IsNullOrEmpty(clientSecret)) {
if (client.IsValidClientSecret(clientSecret)) {
return ClientAuthenticationResult.ClientAuthenticated;
} else { // invalid client secret
return ClientAuthenticationResult.ClientAuthenticationRejected;
}
} else { // no client secret provided
return ClientAuthenticationResult.ClientIdNotAuthenticated;
}
If I take a look at MessageValidationBindingElement.cs:
if (authenticatedClientRequest != null) {
string clientIdentifier;
var result = this.clientAuthenticationModule.TryAuthenticateClient(this.AuthServerChannel.AuthorizationServer, authenticatedClientRequest, out clientIdentifier);
switch (result) {
case ClientAuthenticationResult.ClientAuthenticated:
break;
case ClientAuthenticationResult.NoAuthenticationRecognized:
case ClientAuthenticationResult.ClientIdNotAuthenticated:
// The only grant type that allows no client credentials is the resource owner credentials grant.
AuthServerUtilities.TokenEndpointVerify(resourceOwnerPasswordCarrier != null, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch);
break;
default:
AuthServerUtilities.TokenEndpointVerify(false, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch);
break;
}
Espacially the comment The only grant type that allows no client credentials is the resource owner credentials grant. makes me wonder. Does that mean that in my scenario the JS client should send username/password along? Nope this will raise the following exception:
AccessTokenResourceOwnerPasswordCredentialsRequest parameter 'grant_type' to have value 'password' but had 'refresh_token' instead.
Which is okay to me, because I don't want the client to keep the password.
So here my questions:
Did I unterstand something fundamentally wrong about the password-grant, refresh_token scheme?
As I see it in a JS client the client_id is public knowledge, so it does not serve any security purpose. Am I correct?
Does it makes sense to change DotNetOpenAuth to make use of the ClientType.Public?
Would it make any difference if I just use client_id and client_secret as not secret? E.g. just supply dummy values? What are the security implications?

Resources