I'm using the Flickrj API to log into flickr. For READ only access its fine, but I can't seem to correctly auth when i need WRITE access to add tags to photos.
As i understand the basic auth flow
Get a frob
Pass that frob requesting WRITE access, this returns a URL.
Call the URL to recieve a flickr token
Use the token in all subsequent requests
My code currently is
Flickr f = new Flickr(properties.getProperty(APIKEY),properties.getProperty(SECRET),t);
System.out.println(f.toString());
// 1 get a frob
AuthInterface authInterface = f.getAuthInterface();
String frob = authInterface.getFrob();
System.out.println("first frob "+frob);
// 2 get a request URL
URL url = f.getAuthInterface().buildAuthenticationUrl(Permission.WRITE,frob);
System.out.println(url.toString());
// 3 call the auth URL
// 4 get token
f.getAuthInterface().getToken(frob);
As you can see - i'm stuck on step 3?
I found this code de.elmar_baumann.jpt.plugin.flickrupload.Authorization. After step 2 the trick is to have the java desktop app open a browser window and a dialog. Once the user has logged in via the browser, they click the dialog so step four can be called and the token retrieved.
public boolean authenticate() {
try {
Flickr flickr = new Flickr("xx", "yy", new REST());
Flickr.debugStream = false;
requestContext = RequestContext.getRequestContext();
authInterface = flickr.getAuthInterface();
frob = authInterface.getFrob();
token = properties.getProperty(KEY_TOKEN);
if (token == null) {
authenticateViaWebBrowser();
} else {
auth = new Auth();
auth.setToken(token);
}
requestContext.setAuth(auth);
authenticated = true;
return true;
} catch (Exception ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
JOptionPane.showMessageDialog(null, Bundle.getString("Auth.Error"));
}
return false;
}
private void authenticateViaWebBrowser() throws Exception {
URL url = authInterface.buildAuthenticationUrl(Permission.DELETE, frob);
LargeMessagesDialog dlg = new LargeMessagesDialog(Bundle.getString("Auth.Info.GetToken.Browse", url.toExternalForm()));
dlg.setVisible(true);
Desktop.getDesktop().browse(url.toURI());
JOptionPane.showMessageDialog(null, Bundle.getString("Auth.Info.GetToken.Confirm"));
auth = authInterface.getToken(frob);
token = auth.getToken();
properties.setProperty(KEY_TOKEN, token);
}
I have a error, The code granted me no read permissions.. And I dont know why...
But otherwise I have a Frog and a Token.. And It works !!
// Step 1) Get Frob
AuthInterface ai = f.getAuthInterface();
String frob = ai.getFrob();
System.out.println("frob: "+frob); //--> It Works !!
// Step 2) URL With Permissions
URL uc = ai.buildAuthenticationUrl(Permission.READ, frob);
String request = uc.toExternalForm();
uc.openConnection();
// Step 3) Call URL
System.out.println(request);
URI uri = new URI(request);
Desktop desktop = null;
if (Desktop.isDesktopSupported())
{
desktop = Desktop.getDesktop();
}
if (desktop != null)
{
desktop.browse(uri); // Open Explorer to Confirm
}
// Sleep until accepted in the explorer. After Press enter in Console
BufferedReader infile = new BufferedReader ( new InputStreamReader (System.in) );
String line = infile.readLine();
// Step 4) Get a token
Auth atoken = ai.getToken(frob); // Get a Token with a frob
String stoken = atoken.getToken(); // Get a token like String
System.out.println("Token: "+stoken);
Auth au = ai.checkToken(stoken); // Check token
RequestContext.getRequestContext().setAuth(au);
Related
i'm developing for the first time using AWS Cognito in Java.
I created a code for an Admin to create a User. The user will be automatically created with the status FORCE_CHANGE_PASSWORD. What i was going to do now is a simple login, but if the system return a CHANGE_PASSWORD challenge, then it will open another window where the user should input old password and new password, then submit them to cognito.
The code i used to create a user through AdminCreateUser is the following:
// Creating instance of client CognitoIdentityProvider
CognitoIdentityProviderClient cognitoClient = CognitoIdentityProviderClient.builder().region(Region.EU_CENTRAL_1).build();
AdminCreateUserRequest requestUserCreation = AdminCreateUserRequest.builder()
.username(usernameTextField.getText())
.desiredDeliveryMediums(DeliveryMediumType.EMAIL)
.userAttributes(AttributeType.builder()
.name("email")
.value(emailTextField.getText())
.build())
.userPoolId("xxxxx")
.build();
// Sending sign up request
AdminCreateUserResponse responseUserCreation = cognitoClient.adminCreateUser(requestUserCreation);
// Saving the group we want to put the user in through a combobox
String groupname = (String) groupComboBox.getValue();
UserType newUser = responseUserCreation.user();
GroupType group = GroupType.builder().groupName(groupname).build();
AdminAddUserToGroupRequest addUserToGroupRequest = AdminAddUserToGroupRequest.builder()
.userPoolId("xxxxx")
.username(newUser.username())
.groupName(groupname)
.build();
AdminAddUserToGroupResponse addUserToGroupResult = cognitoClient.adminAddUserToGroup(addUserToGroupRequest);
This code works. When i submit this through a button, an email arrives to the user i created, and it also shows in my Amazon Cognito console.
Now the login part is giving me trouble.
As i said, i want to open another windows which has the right form for resetting the password. I still haven't thought about the implementation for resetting the password because my login doesnt' work, so i will implement this later.
This is my login code:
public void Login(ActionEvent event) {
final String CLIENT_ID = cs.getAppClientId();
final String USER_NAME = userNameTextField.getText();
final String PASSWORD = passwordTextField.getText();
final Region region = cs.getRegion();
CognitoIdentityProviderClient cognitoClient = CognitoIdentityProviderClient.builder()
.credentialsProvider(DefaultCredentialsProvider.create())
.region(region)
.build();
InitiateAuthRequest authRequest = InitiateAuthRequest.builder()
.clientId(CLIENT_ID)
.authFlow("USER_PASSWORD_AUTH")
.authParameters(createAuthParameters(USER_NAME, PASSWORD))
.build();
try {
InitiateAuthResponse authResult = cognitoClient.initiateAuth(authRequest);
if (authResult.challengeName() != null) {
if (authResult.challengeName().equals(ChallengeNameType.NEW_PASSWORD_REQUIRED.toString())) {
try {
reimpostaPassword.apriSchermataReimpostaPassword(event);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
// The authentication was successful
AuthenticationResultType authenticationResult = authResult.authenticationResult();
System.out.println("Access token: " + authenticationResult.accessToken());
}
}
} catch (NotAuthorizedException e) {
System.out.println("Incorrect username or password");
} catch (PasswordResetRequiredException e) {
System.out.println("Password reset is required for the user");
}
When i fill my form with the right username and password, it gives me this error:
Caused by: java.lang.NullPointerException: Cannot invoke "software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType.accessToken()" because "authenticationResult" is null
at com.example.ratatouille23/com.example.ratatouille23.Login.LoginController.Login(LoginController.java:101)
at com.example.ratatouille23/com.example.ratatouille23.Login.LoginController.clickPulsanteLogin(LoginController.java:66)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
... 51 more
The line that gives me error is this:
System.out.println("Access token: " + authenticationResult.accessToken());
This is an odd behaviour, because this means that the 'if the challenge is new password required' control fails, so i'm a little stuck here.
Any help?
I am working on this use case now. To get a very similiar example working, I created a user pool with an App that lets me use these Authentication flows.
When I execute my AWS SDK for Java V2 code that uses identityProviderClient.adminInitiateAuth() - I successfully get an Access Token - as shown here.
WHen i speicfy an incorrect password, I get exception as expected.
Here is a Java code example. To run this Java code example, create a new user in the specified user pool with a temporary password. You will get back a challenge type value of NEW_PASSWORD_REQUIRED in the response.
You cannot read the access token. This code then changes the temporary password to a permanent password. Now the user can log in with the permanent password and you can read the access token.
public class GetAccessToken {
public static void main(String[]args) {
final String usage = "\n" +
"Usage:\n" +
" <clientId> <poolId> <username> <tempPassword> <permanentPassword>\n\n" +
"Where:\n" +
" clientId - The app client Id value that you can get from the AWS CDK script.\n\n" +
" poolId - The pool Id that has the user. \n\n" +
" username - The new user name with a temp password. \n\n" +
" tempPassword - The temp password. \n\n" +
" permanentPassword - The permanent password. \n\n" ;
if (args.length != 5) {
System.out.println(usage);
System.exit(1);
}
String clientId = args[0];
String poolId = args[1];
String username = args[2];
String tempPassword = args[3];
String permanentPassword = args[4];
CognitoIdentityProviderClient identityProviderClient = CognitoIdentityProviderClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(ProfileCredentialsProvider.create())
.build();
boolean wasLoggedIn = getToken(identityProviderClient, clientId, username, tempPassword, poolId);
if (wasLoggedIn)
System.out.println(username +" successfully authenticated");
else {
// Change the temp password to a permanent one and then call getToken() again. Now you will
// get access tokens.
changeTempPassword(identityProviderClient, username, permanentPassword, poolId);
getToken(identityProviderClient, clientId, username, permanentPassword, poolId);
System.out.println(username +" successfully authenticated");
}
}
public static boolean getToken(CognitoIdentityProviderClient identityProviderClient, String clientId, String username, String password, String poolId) {
final Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", username);
authParams.put("PASSWORD", password);
AdminInitiateAuthRequest authRequest = AdminInitiateAuthRequest.builder()
.clientId(clientId)
.userPoolId(poolId)
.authParameters(authParams)
.authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.build();
try {
// If you specify an incorrect username/password, an exception is thrown.
AdminInitiateAuthResponse response = identityProviderClient.adminInitiateAuth(authRequest);
// Get the Challenge type
if (response.challengeNameAsString() == null) {
System.out.println("Access Token Type : " + response.authenticationResult().tokenType());
System.out.println("Access Token : " + response.authenticationResult().accessToken());
return true;
} else if (response.challengeNameAsString().compareTo("NEW_PASSWORD_REQUIRED") == 0) {
System.out.println("The User must change their password. ");
}
} catch(CognitoIdentityProviderException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return false;
}
public static void changeTempPassword(CognitoIdentityProviderClient identityProviderClient, String username, String newPassword, String poolId){
try {
AdminSetUserPasswordRequest passwordRequest = AdminSetUserPasswordRequest.builder()
.username(username)
.userPoolId(poolId)
.password(newPassword)
.permanent(true)
.build();
identityProviderClient.adminSetUserPassword(passwordRequest);
System.out.println("The password was successfully changed");
} catch(CognitoIdentityProviderException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
}
}
SO the reason why you get this NULL Exception is because you need to set the permanent password for the user. If the challengeName=NEW_PASSWORD_REQUIRED, you cannot read the access token.
I am trying to fetch google contacts for a user via oAuth2 mechanism. I am following this tutorial - https://developers.google.com/identity/sign-in/web/server-side-flow
I have javascript code that calls start() on pageload -
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: 'SOME_CLEINT_ID',
scope: 'https://www.googleapis.com/auth/contacts.readonly'
});
});
}
and
auth2.grantOfflineAccess().then(signInCallback);
and then -
function signInCallback(authResult) {
if (authResult['code']) {
var callback = function(data){
data = JSON.parse(data);
console.log(data);
};
callAjax({action: 'saveGmailAuth', gaccesscode: authResult['code']}, callback, true);
} else {
// There was an error.
}
}
This front end code calls my backend Java web servlet, which tries to get access token -
String authCode = request.getParameter("gaccesscode");
String REDIRECT_URI = "";
String CLIENT_SECRET_FILE = "G:/eclipse_proj/GoogleContacts/CLIENT_JSON_FILE.json";
GoogleClientSecrets clientSecrets;
try {
clientSecrets = GoogleClientSecrets.load(JacksonFactory.getDefaultInstance(),
new FileReader(CLIENT_SECRET_FILE));
REDIRECT_URI = clientSecrets.getDetails().getRedirectUris().get(0);
GoogleAuthorizationCodeTokenRequest tokenRequest = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(),
JacksonFactory.getDefaultInstance(), "https://www.googleapis.com/oauth2/v3/token",
clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret(), authCode,
REDIRECT_URI);
GoogleTokenResponse tokenResponse = tokenRequest.execute();
String accessToken = tokenResponse.getAccessToken();
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Every time I try this java code, every time it gives me error at tokenRequest.execute() -
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "redirect_uri_mismatch",
"error_description" : "Bad Request"
}
With REDIRECT_URI as empty string, it give another error saying - redirect_uri_not_provided.
I tried it with both "https://www.googleapis.com/oauth2/v3/token" and "https://www.googleapis.com/oauth2/v4/token"
I need help figuring this out. What am I doing wrong here?
My redirect URI is - http://localhost:8080/GoogleContacts/Callback in both json file and in developer console for oauth2.
For redirect_uri in using Google APIs,go to your Google Dev console and type what you see as is:
//you can use any port you want
http:localhost:8080/oauth2callback
oauth2callback is the key ingredient.
I have a ViewPager and three webservice calls are made when ViewPager is loaded simultaneously.
When first one returns 401, Authenticator is called and I refresh the token inside Authenticator, but remaining 2 requests are already sent to the server with old refresh token and fails with 498 which is captured in Interceptor and app is logged out.
This is not the ideal behaviour I would expect. I would like to keep the 2nd and 3rd request in the queue and when the token is refreshed, retry the queued request.
Currently, I have a variable to indicate if token refresh is ongoing in Authenticator, in that case, I cancel all subsequent request in the Interceptor and user has to manually refresh the page or I can logout the user and force user to login.
What is a good solution or architecture for the above problem using okhttp 3.x for Android?
EDIT: The problem I want to solve is in general and I would not like to sequence my calls. i.e. wait for one call to finish and refresh the token and then only send rest of the request on the activity and fragment level.
Code was requested. This is a standard code for Authenticator:
public class CustomAuthenticator implements Authenticator {
#Inject AccountManager accountManager;
#Inject #AccountType String accountType;
#Inject #AuthTokenType String authTokenType;
#Inject
public ApiAuthenticator(#ForApplication Context context) {
}
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Invaidate authToken
String accessToken = accountManager.peekAuthToken(account, authTokenType);
if (accessToken != null) {
accountManager.invalidateAuthToken(accountType, accessToken);
}
try {
// Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
if (accessToken != null) {
Request.Builder requestBuilder = response.request().newBuilder();
// Add headers with new refreshToken
return requestBuilder.build();
} catch (Throwable t) {
Timber.e(t, t.getLocalizedMessage());
}
}
return null;
}
}
Some questions similar to this:
OkHttp and Retrofit, refresh token with concurrent requests
You can do this:
Add those as data members:
// these two static variables serve for the pattern to refresh a token
private final static ConditionVariable LOCK = new ConditionVariable(true);
private static final AtomicBoolean mIsRefreshing = new AtomicBoolean(false);
and then on the intercept method:
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
// 1. sign this request
....
// 2. proceed with the request
Response response = chain.proceed(request);
// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
if (!TextUtils.isEmpty(token)) {
/*
* Because we send out multiple HTTP requests in parallel, they might all list a 401 at the same time.
* Only one of them should refresh the token, because otherwise we'd refresh the same token multiple times
* and that is bad. Therefore we have these two static objects, a ConditionVariable and a boolean. The
* first thread that gets here closes the ConditionVariable and changes the boolean flag.
*/
if (mIsRefreshing.compareAndSet(false, true)) {
LOCK.close();
/* we're the first here. let's refresh this token.
* it looks like our token isn't valid anymore.
* REFRESH the actual token here
*/
LOCK.open();
mIsRefreshing.set(false);
} else {
// Another thread is refreshing the token for us, let's wait for it.
boolean conditionOpened = LOCK.block(REFRESH_WAIT_TIMEOUT);
// If the next check is false, it means that the timeout expired, that is - the refresh
// stuff has failed.
if (conditionOpened) {
// another thread has refreshed this for us! thanks!
// sign the request with the new token and proceed
// return the outcome of the newly signed request
response = chain.proceed(newRequest);
}
}
}
}
// check if still unauthorized (i.e. refresh failed)
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
... // clean your access token and prompt for request again.
}
// returning the response to the original request
return response;
}
In this way you will only send 1 request to refresh the token and then for every other you will have the refreshed token.
It is important to note, that accountManager.blockingGetAuthToken (or the non-blocking version) could still be called somewhere else, other than the interceptor. Hence the correct place to prevent this issue from happening would be within the authenticator.
We want to make sure that the first thread that needs an access token will retrieve it, and possible other threads should just register for a callback to be invoked when the first thread finished retrieving the token.
The good news is, that AbstractAccountAuthenticator already has a way of delivering asynchronous results, namely AccountAuthenticatorResponse, on which you can call onResult or onError.
The following sample consists of 3 blocks.
The first one is about making sure that only one thread fetches the access token while other threads just register their response for a callback.
The second part is just a dummy empty result bundle. Here, you would load your token, possibly refresh it, etc.
The third part is what you do once you have your result (or error). You have to make sure to call the response for every other thread that might have registered.
boolean fetchingToken;
List<AccountAuthenticatorResponse> queue = null;
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
synchronized (this) {
if (fetchingToken) {
// another thread is already working on it, register for callback
List<AccountAuthenticatorResponse> q = queue;
if (q == null) {
q = new ArrayList<>();
queue = q;
}
q.add(response);
// we return null, the result will be sent with the `response`
return null;
}
// we have to fetch the token, and return the result other threads
fetchingToken = true;
}
// load access token, refresh with refresh token, whatever
// ... todo ...
Bundle result = Bundle.EMPTY;
// loop to make sure we don't drop any responses
for ( ; ; ) {
List<AccountAuthenticatorResponse> q;
synchronized (this) {
// get list with responses waiting for result
q = queue;
if (q == null) {
fetchingToken = false;
// we're done, nobody is waiting for a response, return
return null;
}
queue = null;
}
// inform other threads about the result
for (AccountAuthenticatorResponse r : q) {
r.onResult(result); // return result
}
// repeat for the case another thread registered for callback
// while we were busy calling others
}
}
Just make sure to return null on all paths when using the response.
You could obviously use other means to synchronize those code blocks, like atomics as shown by #matrix in another response. I made use of synchronized, because I believe this to be the easiest to grasp implementation, since this is a great question and everyone should be doing this ;)
The above sample is an adapted version of an emitter loop described here, where it goes into great detail about concurrency. This blog is a great source if you're interested in how RxJava works under the hood.
You can try with this application level interceptor
private class HttpInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//Build new request
Request.Builder builder = request.newBuilder();
builder.header("Accept", "application/json"); //if necessary, say to consume JSON
String token = settings.getAccessToken(); //save token of this request for future
setAuthHeader(builder, token); //write current token to request
request = builder.build(); //overwrite old request
Response response = chain.proceed(request); //perform request, here original request will be executed
if (response.code() == 401) { //if unauthorized
synchronized (httpClient) { //perform all 401 in sync blocks, to avoid multiply token updates
String currentToken = settings.getAccessToken(); //get currently stored token
if(currentToken != null && currentToken.equals(token)) { //compare current token with token that was stored before, if it was not updated - do update
int code = refreshToken() / 100; //refresh token
if(code != 2) { //if refresh token failed for some reason
if(code == 4) //only if response is 400, 500 might mean that token was not updated
logout(); //go to login screen
return response; //if token refresh failed - show error to user
}
}
if(settings.getAccessToken() != null) { //retry requires new auth token,
setAuthHeader(builder, settings.getAccessToken()); //set auth token to updated
request = builder.build();
return chain.proceed(request); //repeat request with new token
}
}
}
return response;
}
private void setAuthHeader(Request.Builder builder, String token) {
if (token != null) //Add Auth token to each request if authorized
builder.header("Authorization", String.format("Bearer %s", token));
}
private int refreshToken() {
//Refresh token, synchronously, save it, and return result code
//you might use retrofit here
}
private int logout() {
//logout your user
}
}
You can set interceptor like this to okHttp instance
Gson gson = new GsonBuilder().create();
OkHttpClient httpClient = new OkHttpClient();
httpClient.interceptors().add(new HttpInterceptor());
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BuildConfig.REST_SERVICE_URL)
.setClient(new OkClient(httpClient))
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.BASIC)
.build();
remoteService = restAdapter.create(RemoteService.class);
Hope this helps!!!!
I found the solution with authenticator, the id is the number of the request, only for identification. Comments are in Spanish
private final static Lock locks = new ReentrantLock();
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(#NonNull Route route,#NonNull Response response) throws IOException {
Log.e("Error" , "Se encontro un 401 no autorizado y soy el numero : " + id);
//Obteniendo token de DB
SharedPreferences prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db)){
locks.lock();
try{
//Obteniendo token de DB
prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db2 = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db2)){
//Refresh token
APIClient tokenClient = createService(APIClient.class);
Call<AccessToken> call = tokenClient.getRefreshAccessToken(API_OAUTH_CLIENTID,API_OAUTH_CLIENTSECRET, "refresh_token", mToken.getRefreshToken());
retrofit2.Response<AccessToken> res = call.execute();
AccessToken newToken = res.body();
// do we have an access token to refresh?
if(newToken!=null && res.isSuccessful()){
String refreshToken = newToken.getRefreshToken();
Log.e("Entra", "Token actualizado y soy el numero : " + id + " : " + refreshToken);
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
prefs.edit().putBoolean("log_in", true).apply();
prefs.edit().putString("access_token", newToken.getAccessToken()).apply();
prefs.edit().putString("refresh_token", refreshToken).apply();
prefs.edit().putString("token_type", newToken.getTokenType()).apply();
locks.unlock();
return response.request().newBuilder()
.header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
.build();
}else{
//Dirigir a login
Log.e("redirigir", "DIRIGIENDO LOGOUT");
locks.unlock();
return null;
}
}else{
//Ya se actualizo tokens
Log.e("Entra", "El token se actualizo anteriormente, y soy el no : " + id );
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String type = prefs.getString("token_type","");
String access = prefs.getString("access_token","");
locks.unlock();
return response.request().newBuilder()
.header("Authorization", type + " " + access)
.build();
}
}catch (Exception e){
locks.unlock();
e.printStackTrace();
return null;
}
}
return null;
}
});
We're using stormpath with Java & also trying to combine form Login with REST API authentication on the same application.
I've setup stormpath servlet plugin as described here https://docs.stormpath.com/java/servlet-plugin/quickstart.html... This works very fine.
Now, on the same application, we have APIs where I've implemented oAuth authentication with stormpath see here http://docs.stormpath.com/guides/api-key-management/
The first request for an access-token works fine by sending Basic Base64(keyId:keySecret) in the request header and grant_type = client_credentials in the body. Access tokens are being returned nicely. However trying to authenticate subsequent requests with the header Bearer <the-obtained-access-token> does not even hit the application before
returning the following json error message...
{
"error": "invalid_client",
"error_description": "access_token is invalid."
}
This is confusing because I've set breakpoints all over the application and I'm pretty sure that the API request doesn't hit the anywhere within the application before stormpath kicks in and returns this error. And even if stormpath somehow intercepts the request before getting to the REST interface, this message doesn't make any sense to me because i'm certainly making the subsequent API calls with a valid access-token obtained from the first call to get access-token.
I have run out of ideas why this could be happening but i'm suspecting that it may have something to do with stormpath config especially with a combination
of form Login/Authentication for web views and oAuth Athentication for REST endpoints. With that said, here's what my stormpath.properties looks like. Hope this could help point at anything I may be doing wrong.
stormpath.application.href=https://api.stormpath.com/v1/applications/[app-id]
stormpath.web.filters.authr=com.app.security.AuthorizationFilter
stormpath.web.request.event.listener = com.app.security.AuthenticationListener
stormpath.web.uris./resources/**=anon
stormpath.web.uris./assets/**=anon
stormpath.web.uris./v1.0/**=anon
stormpath.web.uris./** = authc,authr
stormpath.web.uris./**/**=authc,authr
Help with this would be highly appreciated.
The problem might be related to an incorrect request.
Is it possible for you to try this code in your app?:
private boolean verify(String accessToken) throws OauthAuthenticationException {
HttpRequest request = createRequestForOauth2AuthenticatedOperation(accessToken);
AccessTokenResult result = Applications.oauthRequestAuthenticator(application)
.authenticate(request);
System.out.println(result.getAccount().getEmail() + " was successfully verified, you can allow your protect operation to continue");
return true;
}
private HttpRequest createRequestForOauth2AuthenticatedOperation(String token) {
try {
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Authorization", new String[]{"Bearer " + token});
HttpRequest request = HttpRequests.method(HttpMethod.GET)
.headers(headers)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I've prepared an example that demonstrates oauth token creation as well as authorized access to protected pages using access tokens.
It builds off of the servlet example in the Stormpath SDK. The repo can be found here: https://github.com/stormpath/stormpath-java-oauth-servlet-sample
It demonstrates running a servlet application and having an out-of-band program get and use oauth tokens to access protected resources.
The core of the oauth part is in TokenAuthTest.java:
public class TokenAuthTest {
public static void main(String[] args) throws Exception {
String command = System.getProperty("command");
if (command == null || !("getToken".equals(command) || "getPage".equals(command))) {
System.err.println("Must supply a command:");
System.err.println("\t-Dcommand=getToken OR");
System.err.println("\t-Dcommand=getPage OR");
System.exit(1);
}
if ("getToken".equals(command)) {
getToken();
} else {
getPage();
}
}
private static final String APP_URL = "http://localhost:8080";
private static final String OAUTH_URI = "/oauth/token";
private static final String PROTECTED_URI = "/dashboard";
private static void getToken() throws Exception {
String username = System.getProperty("username");
String password = System.getProperty("password");
if (username == null || password == null) {
System.err.println("Must supply -Dusername=<username> -Dpassword=<password> on the command line");
System.exit(1);
}
PostMethod method = new PostMethod(APP_URL + OAUTH_URI);
method.setRequestHeader("Origin", APP_URL);
method.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
method.addParameter("grant_type", "password");
method.addParameter("username", username);
method.addParameter("password", password);
HttpClient client = new HttpClient();
client.executeMethod(method);
BufferedReader br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String readLine;
while(((readLine = br.readLine()) != null)) {
System.out.println(readLine);
}
}
private static void getPage() throws Exception {
String token = System.getProperty("token");
if (token == null) {
System.err.println("Must supply -Dtoken=<access token> on the command line");
System.exit(1);
}
GetMethod method = new GetMethod(APP_URL + PROTECTED_URI);
HttpClient client = new HttpClient();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " without token...");
int returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
System.out.println();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " with token...");
method.addRequestHeader("Authorization", "Bearer " + token);
returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
}
}
my application suppose to connect a web service and active some of his functions.
first, the application activate a "Login" function that gets as arguments username and password, the function search the user name and the password in a database and returning me if im logged in or not. and creating a session vars for me like:
Session["Username"] = User.Username;
Session["FullName"] = User.FullName;
and more...
and than i want to active another webservice function - UpdateProfile
that change my profile values on the database.
so, my application has a class with some private classes (asynctasks)
and every asynctask is responsible for one function in the webservice.
for example - the login asynctask:
private class LoginAsyncTask extends AsyncTask<String, String, User>
{
private String METHODNAME = "Login";
private String SOAPACTION = "http://tempuri.org/Login";
and more...
in this login asynctask i parse the comming back cookies like this:
cookies is a HashMap<String, String>();
try
{
//respHeaders = trans.call(SOAPACTION, envelope, null);
reshttpHeaders = trans.call(SOAPACTION, envelope, null);
}
catch (Exception e)
{
//connection error.
e.printStackTrace();
return null;
}
cookies.clear();
if (reshttpHeaders!=null) {
for (int i = 0; i < reshttpHeaders.size(); i++) {
HeaderProperty hp = (HeaderProperty)reshttpHeaders.get(i);
String key = hp.getKey();
String value = hp.getValue();
if (key!=null && value!=null) {
if (key.equalsIgnoreCase("set-cookie")){
String cookieString = value.substring(0,value.indexOf(";") );
cookies.put(cookieString.substring(0, cookieString.indexOf("=")),cookieString.substring(cookieString.indexOf("=")+1) );
break;
}
}
}
}
and than, in another asynctask called UpdateProfileAsynctask
im sending this cookie like this:
List<HeaderProperty> httpHeaders = new ArrayList<HeaderProperty>();
for (String cookie:cookies.keySet()) {
httpHeaders.add(new HeaderProperty("Cookie", cookie + "=" + cookies.get(cookie)));
}
try
{
//trans.call(SOAPACTION, envelope, reqHeaders);
trans.call(SOAPACTION, envelope, httpHeaders);
}
when i try to catch this packets with wireshark i see that the cookie that i get is:
Set-Cookie: ASP.NET_SessionId=kmwn4l2qzc0k1anfk1du4ty1; path=/; HttpOnly\r\n
and my cookie that i send is:
Cookie: ASP.NET_SessionId=kmwn4l2qzc0k1anfk1du4ty1\r\n
The problem is that the webservice dont recognize me (the second request is in the 20 minutes period).
this part of the code in the webservice running:
if (Session["Username"] == null)
return "Cant Update profile now, Your connection seems to be timeout";
and i get this message all time. but its stange that sometimes its working :/
thanks.
I fix my problems after reading your questions, thank you.
My code is like the folloiwng:
HeaderProperty headerPropertySessionId = new HeaderProperty("Cookie", "key1=value1");
List headerPropertyList = new ArrayList();
headerPropertyList.add(headerPropertySessionId);