I am using the built in httpclient to issue a "get" query to an external service that needs authentication. More specifically, I am trying to submit queries to splunk from my service. How do I pass in the user credentials in the request? I want to use the basic auth instead of dealing with authentication tokens.
Basic auth is all about the Authorization Header.
You should add that header with a value composed of "basic " (note the blank) and your login:pass (separated by a colon) encoded in base64.
This is only secure if you're using HTTPS.
Here is how I get this done in vert.x :
HttpClient client = vertx.createHttpClient().setSSL(true)
.setTrustAll(true) //You may not want to trust them all
.setHost("api.myawesomeapi.com")
.setPort(443);
HttpClientRequest clientRequest = client.get("/"+action+"/?"+params, new Handler<HttpClientResponse>() {
public void handle(final HttpClientResponse response) {
if (response.statusCode==200){
// It worked !
} else {
// Oops
}
}
});
clientRequest.putHeader(HttpHeaders.Names.AUTHORIZATION, "Basic "+base64key);
Here I already have the base64key, but if I had to create it, I would use something like :
base64key = Base64.encodeBytes(new StringBuilder(apiKey).append(":").append(secretKey).toString().getBytes(), Base64.DONT_BREAK_LINES);
If you use POST instead of get, don't forget to add the required headers :
clientRequest.putHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(params.getBytes().length))
.putHeader(HttpHeaders.Names.CONTENT_TYPE, "application/x-www-form-urlencoded")
.write(params);
I hope it helps
Hugo
Related
I am attempting to authenticate into OKTA using Java's RestAssured API. The authentication type in question is multi-factor, and while I am always able to get this authentication to succeed in Google Chrome, it consistently fails in RestAssured with a 403 despite matching all of the request headers and cookies. My end goal is to get the bearer authentication token in order to be able to run services after I login.
Essentially, this is the sequence of services I'm calling:
1) [redacted]/api/v1/authn; I provide my username and password, as well as some alphanumeric state token. (I use a wrapper class around RestAssured's RequestSpecification:) This returns successfully with a 200:
// Create a brand new request to login using OKTA.
RequestWrapper requestWrapper11 = new RequestWrapper();
requestWrapper11.setResponseContentType(ContentType.JSON);
requestWrapper11.setAcceptedContentType(ContentType.JSON);
requestWrapper11.setHTTPMethod(Method.POST);
requestWrapper11.setRequestPayload(new HashMap<>() {
{
put("username", "[redacted]");
put("password", "[redacted]");
put("options", new HashMap<String, Object>() {
{ put("warnBeforePasswordExpired", true); }
{ put("multiOptionalFactorEnroll", true); }
});
}
});
// Authenticate and get a brand new state token.
requestWrapper11.setBaseURL(new URL("[redacted]/api/v1/authn"));
ResponseWrapper response = requestWrapper11.executeAndGetResponse();
From this service call, I get back a state token that I use in the next step, as well as a factor ID.
2) I then call the POST service [redacted]/api/v1/authn/factors and provide the answer to the security question: (this also returns successfully with a 200):
{answer: "[redacted_1]", stateToken: "[state_token]"}
RequestWrapper requestWrapper2 = new RequestWrapper();
requestWrapper2.setResponseContentType(ContentType.JSON);
requestWrapper2.setAcceptedContentType(ContentType.JSON);
requestWrapper2.setHTTPMethod(Method.POST);
requestWrapper2.setRequestPayload(new HashMap<>() {
{
put("answer", "[redacted]");
put("stateToken", stateToken);
}
});
requestWrapper2.setOverrideQueryParams(new HashMap<>() {
{ put("rememberDevice", false); }
});
// Authenticate and get a brand new state token.
requestWrapper2.setBaseURL(new URL("[redacted]/api/v1/authn/factors/" + factorId + "/verify"));
3) Finally, I make a GET call [redacted]/login/step-up/redirect?stateToken=[state_token] to return a special code used for authentication purposes.
RequestWrapper requestWrapper4 = new RequestWrapper();
// requestWrapper4.setAllowRedirects(true);
requestWrapper4.setOverrideQueryParams(new HashMap<>() {
{put("stateToken",stateToken); }
});
requestWrapper4.setHeader("Connection", "Keep-Alive");
requestWrapper4.setHeader("Host", "[redacted]");
requestWrapper4.setHeader("Accept", "text/html,application/xhtml+xml,application/xml" +
";q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
requestWrapper4.setHeader("Accept-Encoding", "gzip, deflate, br");
requestWrapper4.setHeader("Accept-Language", "en-US,en;q=0.9");
requestWrapper4.setHeader("Sec-Fetch-Dest","document");
requestWrapper4.setHeader("Sec-Fetch-Mode", "navigate");
requestWrapper4.setHeader("Sec-Fetch-Site", "same-origin");
requestWrapper4.addCookies(responseWrapper2.cookies);
requestWrapper4.addCookie("oktaStateToken", stateToken);
requestWrapper4.addCookie("t", "summer");
requestWrapper4.addCookie("DT", "DI0--aZ4ipPS8mFXhEWHFwXUw");
requestWrapper4.addCookie("ADRUM_BTa", "R:0|g:dd262b5c-ae86-4a1d-86aa-a89b3fed2bed|n:Okta_6d5b1e30-d05a-4894-a37b-81b5f6c60e0e");
requestWrapper4.addCookie("ADRUM_BT1", "R:0|i:617|e:41");
requestWrapper4.setHTTPMethod(Method.GET);
requestWrapper4.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36");
requestWrapper4.setBaseURL(new URL("[redacted]/login/step-up/redirect"));
ResponseWrapper responseWrapper4 = requestWrapper4.executeAndGetResponse();
When I authenticate in normally using the browser, these three service calls are made in the browser without issue and all return 200 or 302. However, whenever I run these using the RestAssured API, I always get a 403 on the third service executed, regardless of whether the token passed in as the query parameter {stateToken} is legitimate or not. I always get 400 when this parameter isn't passed in through the RestAssured API, so I know that this method call distinguishes between whether a state token is passed in or not.
My question is: is there anything notable that I'm missing from the set of steps above to cause a 403 to always be returned through one medium but not through a web browser? Is there something in place to prevent authentication using the RestAssured API? If so, is there another route I could take to get the bearer token?
I few things come to mind.
1.) Does your response contain the step up link in response #2?
https://developer.okta.com/docs/reference/api/authn/#response-example-after-authentication-and-mfa-are-complete-for-step-up-authentication-with-okta-session
2.) If possible I'd recommend an OAuth flow (or SAML) instead of using the Authn API
3.) The Authn API is a state machine, you cannot always assume that you can execute those requests in that order.
Okta also has an Authn SDK: https://github.com/okta/okta-auth-java (but again, we recommend OAuth when possible)
I am working on a project in which I need, not only to authenticate but also to have the real value of the token.
We have a Spring Boot application with oAuth2.0 Spring Security and the problem is that I am not able to find a method that gives me a valid token every time I call it.
At this moment, I have a post method raw coded in Java, but there must be a Spring Security implementation that does something like the following:
The first time that it is called, it asks for the token and stores it.
The following times checks if the token has expired and, just if it has expired, it asks for a new one.
Where could I find it?
EDIT
There are 2 different Spring Instances in my project: The Authorization server - which is a Cloud Foundry UAA server - and the resource server - which is the one that asks for the token and is coded by me.
The Authorization server uses AuthorizationServerTokenServices in JWT version and when the Resource server gets a token from there, I want it to be kept, not only decoded and used because I need to send it to another server.
Moreover, my application is not a web app, so there is no login page to log in on Facebook and I have to get the token using the Client Credentials Grant Type.
In my case, Single Sign-On is not possible because I have to use it not decoded.
This is my current implementation:
public String obtainAccessToken() throws ClientProtocolException, IOException {
HttpClient httpclient = HttpClients.createDefault();
String userPass64 = new String("User and password");
HttpPost httppost = new HttpPost("localhost:8080/uaa/oauth/token?grant_type=client_credentials");
httppost.setHeader("Content-Type", "application/x-www-form-urlencoded");
httppost.setHeader("Authorization", "Basic " + userPass64);
//Execute and get the response.
HttpResponse response = httpclient.execute(httppost);
String responseBody = EntityUtils.toString(response.getEntity());
ObjectMapper mapper = new ObjectMapper();
TokenMessage tokenMessage = mapper.readValue(responseBody, TokenMessage.class);
return tokenMessage.getAccess_token();
}
From what I have seen is that there are few different ways that Spring security can handle this.
The default way is to have AuthorizationServerTokenServices interface handle it. And with it you can have different ways of storing the token. For example JDBCTokenStore, InMemoryTokenStore and JwtTokenStore. More about this here : http://projects.spring.io/spring-security-oauth/docs/oauth2.html#managing-tokens
But since I do not know what kind of application you are creating, you could maybe develop a single sign on functionality and let Facebook, for example, handle the authentication Token. Quite good tutorial about that with Spring boot can be found here: https://spring.io/guides/tutorials/spring-boot-oauth2/
Performing any request, I need to perform Authentication with POST request with body {username:"somename", password:"somepass"},
header Content-Type:application.json which gives me a response with generated token, which I need to paste as a second header, smth like Authorization:generated-tokenkjhsdkjfvjbwjbQ== for further requests.
Could you help me with it, please.
Variant which worked for me:
String token = given()
.contentType("application/json")
.body(new User("someuser" , "123"))
.when()
.post(RestConfig.baseUrl+"/authentication-url")
.then().extract().response().as(TokenResponse.class).getToken();
given()
.contentType("application/json")
.header("Authorization", token)
.get(RestConfig.baseUrl+"/some-path")
.then()
.statusCode(200)...
I had a similar requirement, where I had to pass the auth token back and forth, but this was spring rest template not rest assured. For that purpose, I used client filter, which captured the token on response and set it as a header on request. You can search if there is something similar in rest assured, which can do the job.
Here is a sample, https://github.com/rest-assured/rest-assured/wiki/Usage
Custom Authentication
Rest Assured allows you to create custom authentication providers. You do this by implementing the io.restassured.spi.AuthFilter interface (preferably) and apply it as a filter. For example let's say that your security consists of adding together two headers together in a new header called "AUTH" (this is of course not secure). Then you can do that like this (Java 8 syntax):
given().
filter((requestSpec, responseSpec, ctx) -> {
String header1 = requestSpec.getHeaders().getValue("header1");
String header2 = requestSpec.getHeaders().getValue("header2");
requestSpec.header("AUTH", header1 + header2);
return ctx.next(requestSpec, responseSpec);
}).
when().
get("/customAuth").
then().
statusCode(200);
The reason why you want to use a AuthFilter and not Filter is that AuthFilters are automatically removed when doing given().auth().none(). ...
I could be misunderstanding the question, but from what I am getting from it, I think something like this should work:
String token =
given().
header("Content-Type", "application/json").
body(/* body content goes here */).
when().
post(/* route goes here */).
then().
extract().path("token").toString()
// the above path arg depends on the response you get from the call.
Then the next call would be something like:
given().
header("Content-Type", "application/json").
header("Authorization", token).
when()...etc.
Some of the specifics will depend on the API, but I use this format all the time. Often getting a response of a user ID, or a token, etc. and using it for future calls.
More info on extracting in the rest assured docs: https://github.com/rest-assured/rest-assured/wiki/Usage#extracting-values-from-the-response-after-validation
If you want to extract one parameter from response then this should work:
String jsonBody= ( enter request payload here )
ValidatableResponse response = RestAssured.given().baseUri(baseURL)
.accept("application/json")
.header("Content-Type","application/json")
.body(jsonBody).when().post("/auth")
.then().assertThat().statusCode(200)
.log().all();
String token=response.extract().path("token");
as the question allready says, I am trying to do digest authentication in android.
Until now i have used the DefaultHttpClient and it's authentication method (using UsernamePasswordCredentials and so on), but it is deprecated since Android 5 and will be removed in Android 6.
So i am about to switch from DefaultHttpClient to HttpUrlConnection.
Now i am trying to achieve digest authentication, which should work pretty simple as explained here:
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
But the getPasswordAuthentication gets never called for some reason.
During my search for this problem i found different posts, saying digest authentication is not supported by the HttpUrlConnection in android, but those posts are from 2010-2012, so i am not sure if this is still true. Also we are using HttpUrlConnection with digest authentication in our desktop java application, where it does work.
I also found some posts, talking about OkHttp. OkHttp seems to be used by Android under the hood (to be more specific the HttpUrlConnectionImpl). But this HttpUrlConnectionImpl is a bit strange, it is not even shown in the Eclipse type hierarchy and i am not able to debug it. Also it should be a com.squareup.okhttp.internal.huc.HttpUrlConnectionImpl, while in android it is a com.android.okhttp.internal.http.HttpUrlConnectionImpl.
So i am just not able to do digest authentication with this HttpUrlConnection in android.
Can anyone tell me how to do that without external libraries?
EDIT:
The server asks for digest authentication:
WWW-Authenticate: Digest realm="Realm Name",domain="/domain",nonce="nonce",algorithm=MD5,qop="auth"
So Basic-Authentication shouldn' work, as the server is asking for digest.
The answer is, that HttpUrlConnection does not support digest.
You therefore have to implement RFC2617 by yourself.
You can use the following code as a baseline implementation: HTTP Digest Auth for Android.
The steps involve (see RFC2617 for reference):
If you get a 401 response, iterate over all WWW-Authenticate headers and parse them:
Check if algorithm is MD5 or undefined, (optionally select the auth qop option), otherwise ignore the challenge and go to the next header.
Get the credentials using Authenticator.requestPasswordAuthentication.
Calculate H(A1) using the username, realm and password.
Store the canonical root URL, realm, HA1, username, nonce (+ optionally algorithm, opaque and the client selected qop option if present).
Retry the request.
On each request, iterate over all realms you have session information stored for by canonical root URL:
Calculate H(A2) using the request method and path.
Calculate H(A3) using HA1, nonce (+ optionally nc, cnonce, qop) and HA2.
Build and add the Authorization header to your HttpUrlConnection.
Implement some sort of session pruning.
By using Authenticator, you can make sure, that as soon as HttpUrlConnection supports digest natively, your code is not being used anymore (because you wont receive the 401 in the first place).
This is just a quick summary on how to implement it, for you to get an idea.
If you want to go further you would probably like to implement SHA256 as well: RFC7616
It is correct that HttpUrlConnection does not support Digest authentication. If your client must authenticate using Digest, you have a few options:
Write your own HTTP Digest implementation. This can be a good option if you know which servers that you need to authenticate with and can ignore the parts of the the digest specification that you do not need. Here is an example where a subset of digest is implemented: https://gist.github.com/slightfoot/5624590.
Use the external lib bare-bones-digest, which is a Digest lib for Android. You can use it to parse Digest challenges and generate responses to them. It supports the common digest use cases and some of the rarely used ones and can be used on top of HttpURLConnection.
Use OkHttp together with okhttp-digest, which is a plugin that adds Http Digest support to OkHttp. Supporting Digest with OkHttp is easy, just add okhttp-digest as an authenticator and you will have transparent Http digest support. If you already use OkHttp or are OK with switching to it this can be an attractive option.
Use the Apache HttpClient which supports Digest. The question explicitly states that HttpClient is not an option so I include it mostly for completion's sake. Google does not recommend using HttpClient and has deprecated it.
Did you try to set the header manually like:
String basic = "Basic " + new String(Base64.encode("username:password".getBytes(),Base64.NO_WRAP ));
connection.setRequestProperty ("Authorization", basic);
Also be aware of some issues in Jellybeans and a bug when you try to perform a post request: HTTP Basic Authentication issue on Android Jelly Bean 4.1 using HttpURLConnection
EDIT: For Digest authentication
Have a look here https://code.google.com/p/android/issues/detail?id=9579
Especially this might work:
try {
HttpClient client = new HttpClient(
new MultiThreadedHttpConnectionManager());
client.getParams().setAuthenticationPreemptive(true);
Credentials credentials = new UsernamePasswordCredentials("username", "password");
client.getState().setCredentials(AuthScope.ANY, credentials);
List<String> authPrefs = new ArrayList<String>(2);
authPrefs.add(AuthPolicy.DIGEST);
authPrefs.add(AuthPolicy.BASIC);
client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,
authPrefs);
GetMethod getMethod = new GetMethod("your_url");
getMethod.setRequestHeader("Accept", "application/xml");
client.executeMethod(getMethod);
int status = getMethod.getStatusCode();
getMethod.setDoAuthentication(true);
System.out.println("status: " + status);
if (status == HttpStatus.SC_OK) {
String responseBody = getMethod.getResponseBodyAsString();
String resp = responseBody.replaceAll("\n", " ");
System.out.println("RESPONSE \n" + resp);
}
} catch (Exception e) {
e.printStackTrace();
}
I finally replaced the deprecated DefaultHttpClient with my own implementation of the HttpUrlConnection and I implemented digest atuhentication myself, using this as a template.
The finaly code looks something like this:
// requestMethod: "GET", "POST", "PUT" etc.
// Headers: A map with the HTTP-Headers for the request
// Data: Body-Data for Post/Put
int statusCode = this.requestImpl(requestMethod, headers, data);
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED && hasUserNameAndPassword) {
String auth = getResponseHeaderField("WWW-Authenticate");
// Server needs Digest authetication
if(auth.startsWith("Digest")){
// Parse the auth Header
HashMap<String, String> authFields = parseWWWAuthenticateHeader(auth);
// Generate Auth-Value for request
String requestAuth = generateDigestAuth(authFields);
headers.put("Authorization", authStr);
statusCode = this.requestImpl(requestMethod, headers, data);
}
}
So basicly I make a request and if it returns 401, I look, if the server wants digest authentication and if I have username and password. If thats the case, I parse the auth header of the response, which contains all the necessary informations about the authentication.
To parse the auth header I use some kind of StateMachine which is described here.
After parsing the response auth header, I generate the request auth header using the informations from the response:
String digestAuthStr = null;
String uri = getURL().getPath();
String nonce = authFields.get("nonce");
String realm = authFields.get("realm");
String qop = authFields.get("qop");
String algorithm = authFields.get("algorithm");
String cnonce = generateCNonce();
String nc = "1";
String ha1 = toMD5DigestString(concatWithSeparator(":", username, realm, password));
String ha2 = toMD5DigestString(concatWithSeparator(":", requestMethod, uri));
String response = null;
if (!TextUtils.isEmpty(ha1) && !TextUtils.isEmpty(ha2))
response = toMD5DigestString(concatWithSeparator(":", ha1, nonce, nc, cnonce, qop, ha2));
if (response != null) {
StringBuilder sb = new StringBuilder(128);
sb.append("Digest ");
sb.append("username").append("=\"").append(username).append("\", ");
sb.append("realm").append("=\"").append(realm).append("\", ");
sb.append("nonce").append("=\"").append(nonce).append("\", ");
sb.append("uri").append("=\"").append(uri).append("\", ");
sb.append("qop").append("=\"").append(qop).append("\", ");
sb.append("nc").append("=\"").append(nc).append("\", ");
sb.append("cnonce").append("=\"").append(cnonce).append("\"");
sb.append("response").append("=\"").append(response).append("\"");
sb.append("algorithm").append("=\"").append(algorithm).append("\"");
digestAuthStr = sb.toString();
}
To generate the Client-Nonce I am using the following code:
private static String generateCNonce() {
String s = "";
for (int i = 0; i < 8; i++)
s += Integer.toHexString(new Random().nextInt(16));
return s;
}
I hope this helps someone. If the code contains any errors, please let me know so I can fix it. But right now it seems to work.
For Android, I found the bare-bones-digest library worked well: https://github.com/al-broco/bare-bones-digest
Add one line to build.gradle
Use the example code at the above url
Works!
We're trying to connect with another company's custom API which uses two-legged OAuth to authenticate a request and send us a response.
At the moment the code we have is sending a request but it's not being authenticated at the other end and so sending a UNAUTHORISED response.
The steps we have investigated so far:
Connected to the remote site using an OAuth implementation in python using the same credentials.
Asked the other company to compare our OAuth request with another that succeeds to see if there is a anything missing in ours.
After the second point above, the only difference between our request and another working request is that the oauth_token parameter is present in our request and not in others. Furthermore, he said they have an oauth_body_hash_value in most of their requests but that's not present in ours - although they do get working requests without it.
Is there a way to remove the oauth_token parameter in Scribe? Alternatively, is the oauth_body_hash_value always needed? Can a request work without?
I've included the code below, I am completely new to OAuth so please feel free to tell me if there's something else that's wrong.
Note that the TestAPI.class extends DefaultAPI10a and just returns "" for all three required methods.
public class TestImporter {
private static final String REQ_URL = "http://test.com/";
private static final String KEY = "KEY";
private static final String SECRET = "SECRET";
// test variables
private static final String VAR1 = "Test123";
public static void main(String[] args) {
OAuthService service = new ServiceBuilder()
.provider(TestAPI.class)
.apiKey(KEY)
.apiSecret(SECRET)
.build();
Token token = new Token("", "");
OAuthRequest request = new OAuthRequest(Verb.GET, REQ_URL + VAR1 + "/");
service.signRequest(token, request);
Response response = request.send();
System.out.println(response.getBody());
}
}
Regarding your own answer seems that what you want to do is put the signature in the querystring and not use the Authorization header.
This, though valid is not recommended. Anyway if you really need to do it, there's a way of creating the OAuthService to "sign" in the querystring:
ServiceBuilder()
.provider(TestAPI.class)
.apiKey(KEY)
.apiSecret(SECRET)
.signatureType(SignatureType.QueryString)
.build();
Assuming their implementation is not broken, it should not matter that you have 'extra' OAuth headers included. Having said that, the oauth_token header is not optional (I assume you are communicating using OAuth 1.0). This header should contain the access token for the User. In your example you show this token as being blank, which is quite odd!
Now assuming for some reason that it is valid to send blank 'usernames' to this third party system, you will want to make sure that your OAuth signature is matching on both sides (yours and the other companies). Use a protocol sniffer to capture the value of the oauth_signature header, then ask your third party to verify that they generate a signature which is the same. If not then you probably have a signature hashing problem.
It turns out that when we thought we were sending a fully formed HTTP GET request, we weren't.
The library was adding all of the information to the header (where we were getting our information from), but not adding any oauth information to the request Url. I can only assume it's to do with us using two-legged authorisation (hence the empty Token).
By copying the map of oAuthParameters into queryStringParameters, it then allowed the Url to be formed correctly.