Keycloak Identity Broker API - java

So i have a client which consumes an api. The API is secured with keycloak.
Users signs in normally, but i want to allow users to sign in user without having to go keycloak's login page with their social media accounts like facebook or google.
I need a rest API with an implementation of how to get a url generated so when user click on this url in a button, it will take the user to the respective social login page to login while keycloak still serves as the broker.
Below is my implementation, it generates a url alright but does not take the user to google page to login
This is a rest Controller
#Secured("permitAll")
#GetMapping(path = "/generator")
public String brokerGenerator(HttpServletRequest httpServletRequest) throws ServletException {
String provider = "google";
String authServerRootUrl = "http://localhost:8080/";
String realm = "realmName";
String clientId = "clientName";
String nonce = UUID.randomUUID().toString();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
String input = nonce + clientId + provider;
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
String hash = Base64Url.encode(check);
httpServletRequest.getSession().setAttribute("hash", hash);
String redirectUri = "http://localhost:4200/dashboard";
return KeycloakUriBuilder.fromUri(authServerRootUrl)
.path("auth/realms/realmName/google/link")
.queryParam("nonce", nonce)
.queryParam("hash", hash)
.queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
}

Keycloak supports this out of the box. See https://www.keycloak.org/docs/latest/server_admin/index.html#_client_suggested_idp
OIDC applications can bypass the Keycloak login page by specifying a hint on which identity provider they want to use.
This is done by setting the kc_idp_hint query parameter in the Authorization Code Flow authorization endpoint.
UPDATE
In your case you should use normal Keycloak Auth Code Flow endpoint and in addition to the basic query params provide kc_idp_hint param. This way the user is redirected to Keycloak login page first then Keycloak redirects him to the chosen identity provider login page (google in your case).
Here is an example redirect URL:
https://keycloak-domain/realms/REALM_NAME/protocol/openid-connect/auth?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&state=STATE&response_type=code&scope=openid&nonce=NONCE&kc_idp_hint=google
Edit your code according this example:
return KeycloakUriBuilder.fromUri(authServerRootUrl)
.path("realms/realmName/protocol/openid-connect/auth") // Url changed
.queryParam("response_type", "code") // Autherization Code Flow
.queryParam("scope", "openid") // Add additional scopes if needed
.queryParam("kc_idp_hint", "google") // This should match IDP name registered in Keycloak
.queryParam("nonce", nonce)
.queryParam("hash", hash)
.queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
You can manually initiate Keycloak redirection for test. Start normal login flow and when you redirected to Keycloak login page do not enter credentials, instead add kc_idp_hint=google to the URL and hit ENTER. Then you will be redirected right to Google login page.

Related

Oauth2 obtaining token with grant_type client_credentials

I've been trying to obtain a token for oauth2 authentication to connect to a mail server using the following Java code:
public static String getAuthToken(String tenantId, String clientId, String client_secret) throws ClientProtocolException , IOException {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token");
//String scopes = "https://outlook.office365.com/IMAP.AccessAsUser.All";
String scopes = "https://outlook.office365.com/.default"; // we need this scope when we have the grand_type as client credidentials
//String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + client_secret+ "&username="+mailAddress+"&password=" + EMAIL_PASSWORD + "&grant_type=password";
String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + client_secret+ "&username="+mailAddress+ "&grant_type=client_credentials";
loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
CloseableHttpResponse loginResponse = client.execute(loginPost);
InputStream inputStream = loginResponse.getEntity().getContent();
byte[] response = readAllBytes(inputStream);
ObjectMapper objectMapper = new ObjectMapper();
JavaType type = objectMapper.constructType(
objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));
Map<String, String> parsed = new ObjectMapper().readValue(response, type);
return parsed.get("access_token");
}
Yet if I try to get the token without having the password in the request body (if I add the password like in the commented line, all works fine), I cannot connect with that token and get the following authentication failed error:
Exception in thread "main" javax.mail.AuthenticationFailedException: AUTHENTICATE failed. at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732) at javax.mail.Service.connect(Service.java:366) at yk.Auth.main(Auth.java:79)
I've tried adapting the request body (changing/removing the scope etc.) but nothing changes.
What needs to be done in order to obtain a valid token without sending the password as part of the request?
Thank you!
This is a generic answer that could be improved by someone with better knowledge of MS Azure services.
You probably need a token authorized by a user to access the IMAP server. Because your OAuth2 client (identified by clientId) is not the IMAP server user - the token you get using the client_credentials grant is not usable for the IMAP server. The line you commented out uses the password grant (which is deprecated) and requires the user's password.
I think the correct way from the OAuth2 standpoint is to use the auth code flow. This way, the user (resource owner role) delegates their rights (represented by the requested scopes) to your application (OAuth2 client role) which can use them at the IMAP server (resource server role).
The auth code flow requires user interaction - the user's browser gets redirected to the OAuth2 auth endpoint, the user gets authenticated and gives consent with delegating the requested rights to your application. Then your the browser is redirect to the redirect_uri handled by your application. The request has an auth code value which can be exchanged for tokens at the token endpoint of the OAuth2 server.
If your application doesn't interact with users who could delegate their IMAP server access, you probably need to use the password grant or figure out how to make your application (identified by its client_id) an IMAP user and use the client_credentials grant (as you do now).

Can I log in into application with selenium and them get the bearer token generated and use it in api call?Is there any way to get bearer token?

Can you login to application with selenium and then get the bearer token of that session for using it into api call?
I can not login to application with login api as password is salted.. and for performing operations after login using api call, I need bearer token so can you get bearer token ?
Or else can we use login api with salted password generator but salted password functionality is not known!
I have tried using jsexecuter for getting bearer token
I personally would look into JMeter and making the api calls through that.
You can parametize JMeter calls and extract values from responses on JMeter to use on the next call...
Have a look at that.
Selenium doesn't do what you want it to.
You can do that using Playwright, Below is the example code.
public String accessToken = "";
#BeforeClass(alwaysRun = true)
public void getAPITokenFromBrowser() {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false));
Page page = browser.newPage();
page.onRequest(request -> {
if (request.url().equals("www.YOUR_APP_URL" + "/API_PATH_FROM_NETWORK_TAB")) { //if the API Call is found with above condition then spliting using Space because these days api tokens are in format "Bearer abcdsdsdsd"
String[] token = request.headers().get("authorization").split(" ");
this.accessToken = token[1];
System.out.println("AccessToken: " + accessToken);
}
});
page.navigate(YOUR_URL);
page.waitForLoadState(LoadState.LOAD);
page.fill("UsernameXpath", "username");
page.fill("PasswordXpath", "Password");
page.click("LoginBtnXpath");
page.waitForLoadState(LoadState.LOAD);
playwright.close();
}
Once this code block gets executed you will have your token in the variable accessToken. The only important thing here to Identify the API_PATH_FROM_NETWORK_TAB

how to authenticate REST webservice get call using web login page credentials

I have an app A(client), which makes a web-service GET call to App B(server). App B is using web page authentication redirect for all these incoming web service get request calls. AppB is processing GET request some thing like:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// code lines
//....
..
String login_URL = "https://sometestsite.com/pagLogin";
StringBuffer baseURL = request.getRequestURL();
String query = request.getQueryString();
String encReturnURL = URLEncoder.encode(baseURL.toString() + "?" + query, "UTF-8");
String final_URL = login_URL + encReturnURL ;
Cookie[] cookies = request.getCookies();
if ((cookies == null) || (cookies.length == 0))
{
response.sendRedirect(noCookieURL);
return;
}
String cookieValue= null;
for (int i = 0; i < cookies.length; i++)
{
Cookie thisCookie = cookies[i];
String cookieName = thisCookie.getName();
if (cookieName == null)
{
//logger.info("cookieName is null");
}
//logger.info("cookieName is " + cookieName);
if (cookieName.equals("myCookie"))
{
cookieValue = thisCookie.getValue();
break;
}
}
String ESEncypt = esGatekeeper.esGatekeeper(cookieValue,"password");
if(ESEncrypt satisfies some condition){
// construct output message and response
String output = "{Some JSON message}";
response.setContentType("application/json");
response.getWriter().append(output);
}
}
I am working on appA(client) side, to make requests to appB(server), appA is java, REST, spring boot based micro-service.
Question: How can I successfully get through this authentication?
1) In appA I have tried using ApacheHttpClient, and URLConnection to establish a connection to url: https://sometestsite.com/pagLogin. and tried to send cookies to server appB using setRequestProperty("cookieName","value") on HttpURLConnection.
2) as appB uses sendRedirect in case no cookie exist, How to (is it a best practice to) send login crendentials along with get request from appA to appB, so that appB can forward those details when it makes sendRedirect call.
The setup seems to have implemented OAuth2.0 Authorization Code grant type. In OAuth2.0 terminology, the server hosting the login page is called "authorization server", the server hosting the API or any website requiring authentication is called "resource server" and the application trying to consume the api is called "client".
Now, if the "Client" acts on behalf of a user (consider an end user wants to log into a web application), the setup you described is the right setup. Any one of Authorization Code grant type, Implicit grant type and Resource Owner Password Credential grant type can be used and each of them will redirect the user to a login page as you mentioned above.
But when the "Client" is not acting on behalf of any individual user (e.g. a batch job) as in your case, the grant type to be used is Client Credential grant type. Here no redirection to login page will happen. Instead the "client" will directly communicate with the "authorization server" with a client id and client secret and the "authorization server" will return an access code. The client can the communicate with the api in "resource server" with the access code (may be through cookie).
Refer to Client Credential grant type description in RFC 6749 OAuth2.0 specification for complete details.

Authorization using shibboleth sso

We have integrated shibboleth web sso into our application to authenticate the user, Now we want to
do authorization for our application. The below is the process which is i am thinking for authz.
According to shibboleth idp, the unauthenticated user is redirects to login.jsp from idp
Once the user enters the username and password, the page is going to our database
and authenticates the user is valid or not.
Here i want to get the permissions for the user if he is authenticated.
Now again user redirects to idp with some information along with the permissions,
so idp redirects to our service provider with that permissions, no we can control the authorization
for the users.
Here i came to know that i have to deal with attribute-resolver.xml, right now we are using
principle and transientId in this xml. So i know i could get the requierd info(Permissions) from saml response
from shibboleth idp.
So Please tell me, how to deal with attribute-resolver.xml to add our permissions for authorization.
Imp question: What is the better process to do authorization using shibboleth?
Kindly look into the following flow which i am following...
Authentication flow with idp and we are writing our own SP.
1) The below encodeSaml request is going to Idp like following:
public Pair<String,String> getSAMLRequest(String spUrl, String consumerUrl) {
AuthnRequest authnRequest = null;
//String encodedSAMLRequest = null;
Pair<String,String> encodedSAMLRequest = null;
try {
authnRequest = this.buildAuthnRequestObject(spUrl, consumerUrl);
Encoder encoder = Encoder.getEncoder();
encodedSAMLRequest = encoder.encodeAuthnRequest(authnRequest);
} catch (MarshallingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return encodedSAMLRequest;
}
private AuthnRequest buildAuthnRequestObject(String spUrl,
String consumerUrl) {
Issuer issuer = getIssuer();
issuer.setValue(spUrl);
DateTime issueInstant = new org.joda.time.DateTime();
RequestedAuthnContext requestedAuthnContext = getRequestedAuthnContext();
AuthnRequest authRequest = getAuthnRequest(issueInstant, issuer,
consumerUrl, spUrl);
authRequest.setRequestedAuthnContext(requestedAuthnContext);
String systemTime = System.currentTimeMillis() + "";
authRequest.setID("SSOIDSAMLREQ" +systemTime);
authRequest.setVersion(SAMLVersion.VERSION_20);
authRequest.setAssertionConsumerServiceIndex(1);
return authRequest;
}
2) First time idp redirects the user to login.jsp by using configuration which is in the handler.xml using externalAuth
<ph:LoginHandler xsi:type="ph:ExternalAuthn"
externalAuthnPath="/external/login"
supportsForcedAuthentication="true" >
<ph:AuthenticationMethod>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</ph:AuthenticationMethod>
</ph:LoginHandler>
-->Once it comes to the above mentioned path the user is able to see the login.jsp and user will enter the credentials and submitting to the our server to validate the user. So we will get the boolean variable whether the user is valid or not.
-> Once we got status from our server we are preparing the request and response like following which is to be send it to the idp again(AuthenticationEngine.returnToAuthenticationEngine(req,resp)).
request.setAttribute(globalStrings.getForceAuthn(), false);
Principal principal = new UsernamePrincipal(login.getAttributes());
Subject subj = new Subject();
subj.getPrincipals().add(principal);
request.setAttribute(LoginHandler.PRINCIPAL_KEY, principal);
request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, personId);
request.setAttribute(LoginHandler.SUBJECT_KEY, subj);
request.setAttribute(globalStrings.getAuthnMethod(), this.authenticationMethod);
AuthenticationEngine.returnToAuthenticationEngine(request, response);
3) We mention in the attribute-resolver and attribute-filter for the attributes to be released to the SP like below
<resolver:AttributeDefinition id="principal" xsi:type="PrincipalName" xmlns="urn:mace:shibboleth:2.0:resolver:ad">
<resolver:AttributeEncoder xsi:type="enc:SAML2StringNameID" />
<resolver:AttributeEncoder xsi:type="SAML2Base64" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
name="ORG_ATTRIBUTE_64" />
<resolver:AttributeEncoder xsi:type="SAML2String" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
name="ORG_ATTRIBUTE" />
</resolver:AttributeDefinition>
4) So will get the released required attributes from the SP(SAML response) and do further processing(authorization).
Do you own and operate the IdP? If not, you don't have access to attribute-resolver.xml and must lookup attributes in your database when your application receives the principal data.
attribute-resolver.xml is how the IdP gets attributes that may be relevant to multiple applications. All attributes will be resolved even if your application is not allowed to receive a particular attribute. So if you do own the IdP, and think this attribute will be relevant, by all means, load it in the IdP and read it out when your application receives a SAML response from the IdP.
This is all a matter of design, and different designs will be better for different use cases. Also, the more complex the permissions data, the more likely your app should handle it.

Signing in via Twitter OAuth doesn't remember authorization

I am writing a web application and have just implemented that a user can sign in via Twitter, using spring-social-(core/twitter).
However, Twitter behaves strangely. After the initial authentication/authorization, every time I'm sending a user to Twitter for authentication, Twitter prompts to authorize my application again. I've looked into the connected Twitter profile. My app is there and authorized correctly (in my case for read access).
I don't have a case of requesting additional permissions. All my application needs is read access (the authorization dialog confirms this).
I am using the OAuth1Operations (returned by the TwitterConnectionFactory) to do the OAuth dance and save the resulting connection in a database. My front-end is written with Wicket 1.5.
I can work around this behavior by just re-authorizing my app again and again when I want to sign in via Twitter, but this is a big nuisance. Anyone knows what I'm missing here?
Here is my code:
TwitterConnectionFactory connectionFactory = (TwitterConnectionFactory) connectionFactoryLocator.getConnectionFactory(Twitter.class);
String callbackUrl = [...];
if (pageParameters.get("oauth_token").isNull() || pageParameters.get("oauth_verifier").isNull()) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("x_auth_access_type", "read");
OAuthToken token = connectionFactory.getOAuthOperations().fetchRequestToken(callbackUrl, params);
String url = connectionFactory.getOAuthOperations().buildAuthorizeUrl(token.getValue(), OAuth1Parameters.NONE);
getSession().setAttribute("twitter_token", token);
setResponsePage(new RedirectPage(url));
} else {
String token = pageParameters.get("oauth_token").toString();
String verifier = pageParameters.get("oauth_verifier").toString();
OAuthToken previousToken = (OAuthToken) getSession().getAttribute("twitter_token");
if (previousToken.getValue().equals(token)) {
AuthorizedRequestToken authorizedRequestToken = new AuthorizedRequestToken(previousToken, verifier);
OAuthToken accessToken = connectionFactory.getOAuthOperations().exchangeForAccessToken(authorizedRequestToken, null);
Connection<Twitter> connection = connectionFactory.createConnection(accessToken);
}
}
I've found the solution! It is also detailed here: Simple Twitter Oauth authorization asking for credentials every time
The problem was that I specifically requested Twitter to authorize my app every time. Replacing:
String url = connectionFactory.getOAuthOperations().buildAuthorizeUrl(token.getValue(), OAuth1Parameters.NONE);
with
String url = connectionFactory.getOAuthOperations().buildAuthenticateUrl(token.getValue(), OAuth1Parameters.NONE);
solves the issue!
Calling the URL for authentication does only ask for authorization if the app hasn't been authorized yet.

Categories