I have an android app. It connects with a REST API developed with Jersey. My REST End points are secured with Tokens. Below is how I generate them.
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withClaim("userName","myusername)
.withExpiresAt(expirationDate)
.sign(algorithm);
Below is how I validate the token
public boolean validateTokenHMAC256(String token, String secret) throws UnsupportedEncodingException, JWTVerificationException
{
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
Claim usernameClaim = jwt.getClaim("username");
String username = usernameClaim.asString();
System.out.println(username);
return true;
}
In my REST API I have a filter and that filter checks every request to see whether the token is as it is. Below is the code.
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter{
//private static String authorizationSecret = "ZXW24xGr9Dqf9sq5Dp8ZAn5nSnuZwux2QxdvcH3wQGqYteJ5yMTw5T8DBUJPbySR";
public AuthenticationFilter()
{
System.out.println("test printing");
}
#Override
public void filter(ContainerRequestContext crc) throws IOException
{
String headerString = crc.getHeaderString("Bearer");
System.out.println("bluh: "+headerString);
System.out.println("test printing");
try
{
boolean validateToken = validateToken(headerString, AuthKey.authorizationSecret);
System.out.println("valid");
}
catch(Exception e)
{
System.out.println("invalid");
crc.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private boolean validateToken(String strToken, String secret) throws UnsupportedEncodingException, JWTVerificationException
{
Token token = new Token();
return token.validateTokenHMAC256(strToken,secret);
}
}
The above code will be called when the user login to the application. However the token will be expired in 60 minutes. I know that after the token is expired either I have to take the user back to sign in screen or refresh the token. I went through the advices in here and here
But I do not understand the following.
How can I figure out whether the token has to be renewed? I thought I should do that after it is expired, but seems that is not the case. If I ask it to refresh in now<exp it will refresh in every request.
How can I assign and send this token back to the user? Currently when the user login on, he will get the token and he will save it in a variable. For the refreshed token to work, do I have to call the login method again (So the token will be sent to the user) or JWT it self will handle the case?
How do I actually refersh using java-jwt ?
How can I figure out whether the token has to be renewed? I thought I should do that after it is expired, but seems that is not the case. If I ask it to refresh in now
You need to refresh the token before it is expired. Decide your policy:
issue a fresh token in every request
issue a fresh token when the current one is close to expire. e.g. 10 min
let client app request a new token when it needs it using a "refresh service" of your api. For example
#GET
#Path("/jwt/refresh")
#Produces(MediaType.TEXT_HTML)
public String refresh(){
//Build a returns a fresh JWT to client
}
How can I assign and send this token back to the user?
If you issue a fresh token during a request, you can return it in a special header that client will read during processing of the response. If you publish a "refresh" service as described above, then the client will call it independently when the current JWT is close to expire
Redirect to login method is not a good alternative because you will lose the current request
How do I actually refresh using java-jwt
Just issue a new token
Related
I want to be able to fetch a param from the redirect url whenever it is automated. I am having difficulties doing this as I am getting a bad request after I created another endpoint to effect this.
I have an endpoint that works fine. The endpoint is a get method. Loading the endpoint takes a user to a page where they need to provide some necessary details. Once these details have been verified, the user is redirected to my redirecr_uri. The redirect_uri now contains important information like session_id, code, etc. The most important thing I need is the code. I need to pass the code into yet another endpoint which will return an access token.
I have manually done this process and it works but I want it to be done automatically because I can't keep doing that when I push the code to staging or production.
Here is the endpoint that redirects as well as the method.
#GetMapping("/get-token")
public RedirectView getBvn() throws UnirestException {
return nibss.getAccessToken();
}
This is the method that the controller calls
public RedirectView getAccessToken() throws UnirestException {
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/authorize.htm?scope=profile&acr_values=otp&response" +
"_type=code&redirect_uri=https://www.accionmfb.com/&client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e";
RedirectView redirectView = new RedirectView();
redirectView.setUrl(url);
return redirectView;
}
When the user provides the right information they are redirected to something like this
https://www.accionmfb.com/?code=9ad91f13-4698-4030-8a8f-a857e6a9907e&acr_values=otp&scope=profile&session_state=fa525cabc5b62854c73315d0322fd830c12a5941b89fd8e6e518da369e386572.b78a3d21-e98e-4e9a-8d60-afca779d9fad&sid=fd60ab92-ef37-4a5b-99b9-f8f52321985d
It is important to state that this 3rd party API I am trying to consume uses oath2.0 client authentication.
I created this endpoint to get the code from the redirected_uri
#GetMapping("/redirect-url")
public void handleRedirect(#RequestParam("code") String code) throws UnirestException {
if(Objects.nonNull(code) || !code.isEmpty()){
nibss.getToken(code);
log.info("Code is not being passed {}", code);
} else {
log.info("Code is not being passed {}", code);
}
}
public String getToken(String code) throws UnirestException {
log.info("This is the code here oooooooooo {}", code);
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/restv1/token";
String parameters = "client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e&code="+code+"&redirect_uri=https://www.accionmfb.com/&grant_type=authorization_code";
HttpResponse<String> apiResponse = Unirest.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Basic MDkxNWNkMDAtNjdmMi00NzY4LTk5YWMtMWIyZmY5ZjFkYTJlOlRVRnEwcGFBQXRzbzBZOEcxMkl2WFZHUmx6WG5zaERiaGt1dzI1YUM=")
.body(parameters)
.asString();
//JSONObject apiJson = apiResponse.getBody().getObject();
//return apiJson.getString("access_token");
JSONObject json = new JSONObject(apiResponse.getBody());
String accessToken = json.getString("access_token");
log.info(accessToken);
return accessToken;
}
But this is not working, I get 400 whenever I hit the second endpoint. What am I doing wrong?
The redirect_uri that you are passing to the OAuth server is https://www.accionmfb.com which does not include the path /redirect-url so the redirect never hits your method.
Either register and pass a callback uri like redirect_uri=https://www.accionmfb.com/redirect-url
Or change #GetMapping("/redirect-url") to #GetMapping("/")
We have a Java based application with the front-end in Vuejs.
The application has a CsrfRequestMatcher that bypasses GET reqests.
The problem we're facing is with SSO. It is our custom Login/SSO we are not using any third party APIs for it.
This is the use case that breaks our application:
The user successfully SSOs in.
The user can move around the application doing POST requests.
The user doesn't logout, but closes the tab.
The user SSOs in again.
Lands fine in the application, but as soon as they do a POST
request, the application says invalid CSRF token and throws them on the login page.
All this takes place in the org.springframework.security.web.csrf.CsrfFilter class.
Here's the code snippet:
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
Upon debugging I see that the code in CsrfFilter still has the csrfToken it generated the last time on the first call / SSO.
Then the user is redirected to a different page (Still a GET request), and the CsrfFilter is called again.
This time CsrfToken csrfToken = this.tokenRepository.loadToken(request); is null. This reads it from the session so don't understand why is that null?
So it generates a new CsrfToken, and the UI has an old CsrfToken obviously, so the next POST call generates the Invalid CSRF token error, because of token mismatch.
We are using an older version of Spring (1.4), so please take this in to account when you answer.
Any help is appreciated to resolve this CSRF token issue.
I am writing a Resource Server backend for a mobile app front end. The app is responsible for getting a token from Facebook and passing it to the backend for authz, but as they do not use JWT bearer tokens as other providers do (eg. google, apple)... and which Spring has some very nice libraries for... I have had to write this custom bit to validate the token and collect some basic user information.
This works fairly well, but response time can be variably sluggish if I need to run this for each request to my stateless application. I am considering some form of caching for this token after it has been validated (with a ttl of course)... but am generally curious if there are any established techniques I should be using.
#Data
class FacebookDetails {
private String id;
private String name;
private String email;
}
class Facebook {
public AuthenticationProvider getAuthenticationProvider() {
WebClient facebookWebClient = WebClient.create("https://graph.facebook.com/v10.0");
OpaqueTokenIntrospector introspector = = token -> {
Optional<ResponseEntity<FacebookDetails>> maybeResponse = facebookWebClient.get()
.uri("/me?fields=id,name,email&access_token=" + token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(FacebookDetails.class)
.onErrorResume(e -> Mono.empty())
.blockOptional(Duration.ofMinutes(1L));
if (maybeResponse.isEmpty()) {
throw new BadOpaqueTokenException("token is invalid");
}
FacebookDetails details = maybeResponse.get().getBody();
return details.getPrincipal();
};
return new OpaqueTokenAuthenticationProvider(introspector);
}
}
One could cache the access token as long as it is valid for. To find out how long that is, you could interpret the expires_in claim in the JSON response from the Facebook token endpoint, as described here: https://developers.facebook.com/docs/facebook-login/access-tokens/refreshing/
We have a Daemon application that uses the EWS API to access office365/Exchange server with basic authentication. I am trying to implement the Oauth2. There are a lot of documents. However, they are often out of date and caused more confusion. I followed this document https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-overview, which seems up-to-date. I did the following steps:
Register App
Document: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-app-registration
- Registered a secret with application password in Azure AD, i.e. certificate is used. The generated secret is recorded.
- selected the “Accounts in this organizational directory only”.
- Requested API Permission for Application permissions for Exchange full_access_as_app and Mail.Read. Admin consent is granted.
Get Token
Document: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-acquire-token?tabs=java
I prototyped to use Protocol to get token
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id={myAppClientId}
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret={myAppSecret}
&grant_type=client_credentials
I got token with
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "……thetoken…"
}
Call EWS API in my App
My App works with the Basic Authentication. I modified it by adding the Authorization header ("Authorization", "Bearer " + accessToken); Basially the prepareWebRequest() function is overriden by adding the Authorization header. Compared with a Basic Authentication case, the request has the additional Authorization header with the Bearer token.
For the same EWS API call that the Basic Authorization had worked, the response is 401 with
x-ms-diagnostics
2000003;reason="The audience claim value is invalid for current resource. Audience claim is 'https://graph.microsoft.com', request url is 'https://outlook.office365.com/EWS/Exchange.asmx' and resource type is 'Exchange'.";error_category="invalid_resource"
Researched in stackoverflow, people suggested to use the following as scope value to get token in step 2:
https://outlook.office365.com/full_access_as_app
https://outlook.office.com/Mail.Read
I tried and both returned “invalid_scope” error. It seems both worked before but not anymore. Following the working scope value format, I tried to use https://outlook.office.com/.default as scope value. I was able to get a token! However, when I use this token in EWS API to access the mailbox, I got 500 error instead of the 401.
What are the right things to do to make it work? What is the right Scope to access an office365 mail box?
More Code Snippets
This is a new class added for oauth2
package microsoft.exchange.webservices.data;
import java.util.Map;
public final class BearerTokenCredentials extends ExchangeCredentials {
private static final String BEARER_TOKEN_FORMAT_REGEX = "^[-._~+/A-Za-z0-9]+=*$";
private static final String AUTHORIZATION = "Authorization";
private static final String BEARER_AUTH_PREAMBLE = "Bearer ";
private String token;
public String getToken() {
return token;
}
public BearerTokenCredentials(String bearerToken) {
if (bearerToken == null) {
throw new IllegalArgumentException("Bearer token can not be null");
}
this.validateToken(bearerToken);
this.token = bearerToken;
}
protected void validateToken(String bearerToken) throws IllegalArgumentException {
if (!bearerToken.matches(BEARER_TOKEN_FORMAT_REGEX)) {
throw new IllegalArgumentException("Bearer token format is invalid.");
}
}
#Override
public void prepareWebRequest(HttpWebRequest request) {
Map<String, String> headersMap = request.getHeaders();
String bearerValue = BEARER_AUTH_PREAMBLE + token;
headersMap.put(AUTHORIZATION, bearerValue);
//headersMap.put("X-AnchorMailbox","esj_office365_imap#genesyslab.onmicrosoft.com");
request.setHeaders(headersMap);
}
}
Use the token to acceess EWS/Exchange ews-java-api 2.0-patched
ExchangeService service = new
ExchangeService(ExchangeVersion.Exchange2010_SP2); //version is
Exchange2010_SP2
service.setTraceEnabled(true);
BearerTokenCredentials credentials = new BearerTokenCredentials("thetoken");
service.setCredentials(credentials);
service.setUrl(new
URI(host));//https://outloook.office365.com/EWS/Exchange.asmx
try{
Folder.bind(service, WellKnownFolderName.Inbox);
}catch(Exception e)
{
//The remote server returned an error: (500)Internal Server Error
}
The code you use to connect to the Office365 Mailbox still needs to use EWS Impersonation eg
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, mailboxName);
Where MailboxName is the Mailbox you want to connect to.
I was following this tutorial: https://developers.docusign.com/esign-rest-api/code-examples/config-and-auth
(I used java)
In the third step I dont know how to get the code that is sent back form DocuSign as query param in the redirect uri:
// Java request auth token
**String code = "{ENTER_AUTH_CODE_FROM_PREVIOUS_STEP}";**
// assign it to the token endpoint
apiClient.getTokenEndPoint().setCode(code);
// optionally register to get notified when a new token arrives
apiClient.registerAccessTokenListener(new AccessTokenListener() {
#Override
public void notify(BasicOAuthToken token) {
System.out.println("Got a fresh token: " + token.getAccessToken());
}
});
// following call exchanges the authorization code for an access code and updates
// the `Authorization: bearer <token>` header on the api client
apiClient.updateAccessToken();
I get an error saying the requested access token is null. Below is the error:
Error while requesting an access token. No accessTokenResponse object received, maybe a non HTTP 200 received?
Has anybody ever got the same one or could maybe someone tell me how to fix it.
I ran below code and its working fine for me, after getting code from the previous step in the URL:
public static void main(String[] args) {
String IntegratorKey = "[Your_Integrator_Key]";
String ClientSecret = "[Your_Secret_Key]";
String RedirectURI = "https://www.getpostman.com/oauth2/callback";//This REDIRECT_URI should match whats configured with IntegratorKey in your Sandbox account
String AuthServerUrl = "https://account-d.docusign.com";
String RestApiUrl = "https://demo.docusign.net/restapi";
ApiClient apiClient = new ApiClient(AuthServerUrl, "docusignAccessCode", IntegratorKey, ClientSecret);
apiClient.setBasePath(RestApiUrl);
apiClient.configureAuthorizationFlow(IntegratorKey, ClientSecret, RedirectURI);
Configuration.setDefaultApiClient(apiClient);
String code = "{ENTER_AUTH_CODE_FROM_PREVIOUS_STEP}";
apiClient.getTokenEndPoint().setCode(code);
apiClient.registerAccessTokenListener(new AccessTokenListener() {
#Override
public void notify(BasicOAuthToken token) {
System.out.println("Got a fresh token: " + token.getAccessToken());
}
});
apiClient.updateAccessToken();
}
To get the code returned by DocuSign from browser, you need to have a WEBApp to which DocuSign will redirect the browser, this same callback URL should be configured in your DS Sandbox's REDIRECT_URI, for instance if you WEBApp callback URL is http://locahost:8080/docusignapp/callback, then this should be added in REDIRECT_URI in IntegratorKey and same needs to be added when calling DS URL to authenticate a user. Once DS authenticates the user, it will redirect the browser to your APP url. On hitting your WEBApp then you need to read the URL and strip off the code part using Javascript, then run the step2 to get the access Token. A sample JS code to strip the code part is:
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
var authCode = vars["code"];
In the example which you share they did it using Standalone code, where you are manually copying the code part from the URL after authentication and running the step2.