I had expected the refresh of an expired access token to happen during the authentication process instead of during an api access.
I think I understand why this happens - authorization is done once but an access token can expire at any time, therefore a refresh attempt needs to be attempted whenever the token is determined to be expired.
I'd like to confirm this is the right interpretation of what's going on.
My first clue was the part of the docs that said
If you use a Google API Client Library, the client object refreshes
the access token as needed as long as you configure that object for
offline access.
I am using the following:
google-oauth-client 1.24.1
google-oauth-client-java6 1.24.1
google-oauth-client-jetty 1.24.1
When I run with a completely invalid access token ("i am no good") and a valid refresh token and execute a
DCM API call to a com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient subclass, I observe the following behavior:
control passes to com.google.api.client.auth.oauth2.Credential at method:
public final boolean refreshToken() throws IOException {
lock.lock();
try {
try {
TokenResponse tokenResponse = executeRefreshToken();
if (tokenResponse != null) {
setFromTokenResponse(tokenResponse);
for (CredentialRefreshListener refreshListener : refreshListeners)
{
refreshListener.onTokenResponse(this, tokenResponse);
}
return true;
}
} catch (TokenResponseException e) {
boolean statusCode4xx = 400 <= e.getStatusCode() && e.getStatusCode() < 500;
// check if it is a normal error response
if (e.getDetails() != null && statusCode4xx) {
// We were unable to get a new access token (e.g. it may have been revoked), we must now
// indicate that our current token is invalid.
setAccessToken(null);
setExpiresInSeconds(null);
}
for (CredentialRefreshListener refreshListener : refreshListeners) {
refreshListener.onTokenErrorResponse(this, e.getDetails());
}
if (statusCode4xx) {
throw e;
}
}
return false;
} finally {
lock.unlock();
}
}
This goes out and gets a new access token as long as the refresh token is valid (i've tried using an invalid refresh token and watched it fail).
Upon successful retrieval of a new access token, control passes to
refreshListener.onTokenErrorResponse(this, e.getDetails());
The token is inserted into the proper objects and access continues.
If I run with a bad refresh token the above method fails with:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad
Request
{
"error" : "invalid_grant",
"error_description" : "Bad Request"
}
Can anyone confirm I've got the right general idea?
Related
When I am authorising a request, if any of the standard claims in the JWT are invalid, or if it fails for some other reason (such as the signature being incorrect), I would like to be able to see what exactly was incorrect, especially when testing. Currently, I am not able to see any message in the Unauthorized 401 response, nor in my logs.
My authentication setup (in my Application.module() function), using the auth0-jwt library.
val jwtVerifier = JWT.require(Algorithm.RSA256(getPublicKeyFromString(publicKey), null))
.withAudience("audience")
.acceptLeeway(1)
.acceptExpiresAt(5)
.build()
install(Authentication) {
jwt {
verifier(jwtVerifier)
validate { credential: JWTCredential ->
JWTPrincipal(credential.payload)
}
}
}
#OptIn(KtorExperimentalLocationsAPI::class)
install(Locations) // see http://ktor.io/features/locations.html
install(Routing) {
authenticate {
ServiceEndpoints()
}
}
I have set up an endpoint handler as follows:
fun Route.ServiceEndpoints() {
get<Paths.getData> { params ->
checkCustomClaim(context.authentication.principal(), <some other parameters here>)
//handling code here
}
}
I'll point out that checkCustomClaim() will raise an AuthorisationException (just a simple exception that I created) if the custom claim fails. I do it this way because each endpoint will be checking different information in my custom claims.
I have attempted to get logs and more information in the response with a custom status page. I am able to get the log message and response data for my AuthorisationExceptions, but not for failures in the standard claims.
install(StatusPages) {
exception<JWTVerificationException> { cause ->
log.warn("Unauthorized: ${cause.message}")
this.call.respond(
status = HttpStatusCode.Unauthorized,
message = cause.message ?: "Unauthorized"
)
}
exception<AuthorisationException> { cause ->
log.warn("Unauthorized: ${cause.message}")
this.call.respond(
status = HttpStatusCode.Unauthorized,
message = cause.message ?: "Unauthorized"
)
}
}
You can use information from a JWT diagnostics log that is written on the TRACE level.
the
Lately I joined a project that is using Azure AD Open ID connect authentication code to authenticate with the ASP.Net Core web application.
When I am trying to run it locally I am facing issues with retrieving info with the GetAccountAsync method (Return null). From what I read, I think the code is missing a caching helper to cache the user/application tokens.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
var account = await _app.GetAccountAsync(userId);
try
{
var result = await _app.AcquireTokenSilent(_scopes, account).ExecuteAsync();
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
If anyone has any idea what I could do to fix this issue Ill be happy to know :)
Thank you!
I have successfully created a Cognito user pool and identity pool using aws amplify, and am able to use the documented process to login using the provided authUI. Once logged in, I can retrieve a jwt token via the provider response.... (not complete below so ignore any syntax errors)
AWSAuthUIViewController.presentViewController(
with: self.navigationController!, configuration: nil,
completionHandler: { (provider: AWSSignInProvider, error: Error?) in
if error != nil {
print("Error occurred: \(String(describing: error))")
} else {
// Sign in successful.
print("sign in - token = \(provider.token())")
var tokentask = provider.token()
var output = tokentask.result
}
})
I can then use that token (output) to authentication against an API gateway resource successfully. My problem is I cannot get the token at any other time. I cannot find the correct object to use to try to retrieve that token or cannot find the location where the token is cached so I can reuse it at other times in the app. Any assstance would be appreciated!
i've managed to find out the best way -
AWSCognitoUserPoolsSignInProvider.sharedInstance().getUserPool().token().continueWith { (AWSTask) -> Any? in
if AWSTask.error == nil {
print("Token \(String(describing: AWSTask.result))")
}
return nil
}
This returns the token via awstask.
I'm using Oauth2 on my own Web API and ASP.NET C# to consume that API on a web app. On my web app, I'm making HttpWebRequests. When my access token expires, I'm calling a method "RefreshToken" that makes a request to get a new access token. This works beautifully without issue...except that the response I get back contains a new refresh token??? I was expecting just the new access token. I didn't even think this was possible without passing credentials again, but my grant_type=refresh_token is somehow generating a new refresh token, and it has me concerned.
Please see this post by Taiseer Joudeh (which is a phenomenal series of posts by the way).
You will find in the SimpleRefreshTokenProvider's CreateAsync method, the refresh token is deleted and re-created which provides "sliding expiration". If you don't want a new refresh token each time don't delete/recreate.
Here is the line of code I'm talking about:
var result = await tokenRepository.AddRefreshToken(token);
AddRefreshToken actually deletes and re-creates the token as seen here:
public async Task<bool> AddRefreshToken(AspNetRefreshToken token)
{
var existingToken = _context.AspNetRefreshTokens.SingleOrDefault(r => r.Subject == token.Subject && r.ClientId == token.ClientId);
if (existingToken != null)
{
await RemoveRefreshToken(existingToken);
}
_context.AspNetRefreshTokens.Add(token);
return await _context.SaveChangesAsync() > 0;
}
So again, without seeing your code I would say its working as expected. If you don't want sliding expiration, don't have the provider re-create the refresh token each time.
I'm using FOSRestBundle for my REST API and so far it has been a great tool. I use HTTP Basic Auth and in most of the cases it works just fine. However, I have problems with the bundle's exception behaviour when bad credentials are submitted. When handling exceptions (via the integrated authentication handlers or the exception mapping configuration), the bundle always gives me a response with the correct HTTP status and JSON/XML content similar to this:
{
"code": 401,
"message": "You are not authenticated"
}
This is fine, it also works when no authentication information is submitted at all. However, when submitting bad credentials (e.g. unknown username or incorrect password) I get the HTTP code 401 Bad credentials (which is fine) with an empty message body. Instead, I would have expected something similar to the JSON above.
Is it a bug or a configuration issue on my side? I would also love to know how these kinds of authentication errors are exactly handled by the bundle, since overriding the BadCredentialsException's status code in the codes section of the bundle's exception configuration section seems to be ignored.
Thanks!
Alright, after digging into the bundle's code some more, I figured it out. The problem results from the way bad credentials are handled by Symfony's HTTP Basic Authentication impementation. The 401 Bad Credentials response is a custom response created by BasicAuthenticationEntryPoint, which is called by the BasicAuthenticationListener's handle function, immediately after an AuthenticationException has been thrown in the same function. So there is no way of catching this exception with a listener:
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
return;
}
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) {
return;
}
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Basic Authentication Authorization header found for user "%s"', $username));
}
try {
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
$this->securityContext->setToken($token);
} catch (AuthenticationException $failed) {
$this->securityContext->setToken(null);
if (null !== $this->logger) {
$this->logger->info(sprintf('Authentication request failed for user "%s": %s', $username, $failed->getMessage()));
}
if ($this->ignoreFailure) {
return;
}
$event->setResponse($this->authenticationEntryPoint->start($request, $failed));
}
}
The entry point's start function creates the custom response, with no exceptions involved:
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
$response->setStatusCode(401, $authException ? $authException->getMessage() : null);
return $response;
}
The fist if-clause in the handle function above also explains why it works in the case of "no user credentials at all", since in that case, the listener just stops trying to authenticate the user, and therefore an exception will be thrown by Symfony's firewall listeners (not quite sure where exactly), so FOSRestBundle's AccessDeniedListener is able to catch the AuthenticationException and do its thing.
You can extend AccessDeniedListener and tell FOSRestBundle to use your own listener with the parameter %fos_rest.access_denied_listener.class%. (service definition)
parameters:
fos_rest.access_denied_listener.class: Your\Namespace\For\AccessDeniedListener
Then add an additional check for BadCredentialsException and emmit an HttpException with the desired code/message similar to the check for AuthenticationException at Line 70.