Keycloak and nodeJS : Unable to refresh with expired refresh token - node.js

EDIT : I've set a server on a vm, without Docker, and everything worked well, it looks like VM+Docker is too much layer.
I have a node backend, with a route protected with :
app.get('/api/user', keycloak.protect(), function(req, res) {
res.json({ message: 'This is an USER endpoint payload' });
});
I have this keycloak.json :
{
"realm": "MyRealm",
"auth-server-url": "http://keycloak.com:8008/auth/",
"ssl-required": "none",
"resource": "sso_agt",
"public-client": true,
"confidential-port": 0
}
When I use this route, i'm redirected to the auth page. When i'm logged, it redirects me to the right url, but I have denied access, with this error in npm console :
Could not obtain grant code: Error: Unable to refresh with expired refresh token
In my keycloak console, i have a WARN type=CODE_TO_TOKEN_ERROR
I run keycloak with docker, on a Ubuntu Server VM.

Refresh tokens are no-expiration passwords, when combined with the clientId and client service allow for the generation of actual access tokens. Once a refresh token is marked as invalid, there is no way to get a new one without navigating the user through implicit login flow with the scope: offline set.

I've set a server on a vm, without Docker, and everything worked well, it looks like VM+Docker is too much layer.

Related

Keycloak issue Cannot exchange code for grant in bearer-only mode

I'm new to keycloak and was trying to setup a role based authentication using keycloak
with Nodejs but each time I login with my correct username and password which do exist in realm I get this bearer-only grant issue. I have tried all of the solutions and even checked if the access type on Keycloak is Bearer-only but no that is confidential tried setting the bearer-only to true and false as well but nothing worked for me
{
"realm": "realm_name",
"auth-server-url": "Keycloak_auth_url",
"ssl-required": "none",
"resource": "resource",
"verify-token-audience": false,
"public-client":true,
"grant_type":"password",
"credentials": {
"secret": secret_credentials
},
"confidential-port": 0,
"policy-enforcer": {},
"scope":"openid"
}
This is my keycloak.json file
app.get("/", keycloak.protect(), function (req, res) {
console.log(req)
}
)
This is my simple function for protecting a route
You configured your client to use password grant, which doesn‘t use an authentication code. Using this grant, the client gets an access token and optional a refresh token by posting it‘s clients credentials together with the resource owners credentials to the authorization server.
Depending on the type of application that your client is, choose an according grant type.
Nevertheless password grant shouldn’t be used at all, a recent update of the OAuth 2.0 current best practices stated. See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-18

Azure AD callback URL is not getting hit every time

It gets hit only when I clear site data and then try to login with Azure AD. Tried setting cache to no-store for all the requests but still not working.
You need add prompt: 'login', like below in your nodejs project.
passport.authenticate('azuread-openidconnect',
{
response: res,
prompt: 'login',
failureRedirect: '/',
failureFlash: true,
successRedirect: '/'
}
)(req,res,next);
The specific implementation method is modified according to your project. Adding prompt=login in the redirect url can force a password to log in every time. It is no longer necessary to clean up the browser cache and log in with a username and password.
Related Posts:
1. Unable to login more than one account of the same domain when using Azure AD as the IdP for G Suite
2. Providing user parameters in azure active directory authentication

azure .net core app with IS4: web api call fails with "Bearer error=invalid_token The issuer is invalid"

I have a .net Core 3.1 app running in an azure web app for containers(linux) service. This app is a web api project with an angular 9 front end. It uses Identity server 4 for authorization.
For reference I am using this clean architecture framework template for my project (the add docker support pr).
The app runs in the service just fine. The front end works and I can "log in" using ID4 and I can see that the authorization token are returned when I log in.
THE PROBLEM:
When I make a call to the backend web api from the front end angular client app I get the following error:
HTTP/1.1 401 Unauthorized
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The issuer 'https://*********.azurewebsites.net' is invalid"
I am tempted to add a manual setting for the IssuerUri but the identity server 4 docs recommend against doing this so I did not. Maybe having this run in docker makes it different.
I did have to add support for forwarding headers to get IS4 to work properly in startup.cs configureServices according to these docs for proxy load balancers. I had to add ASPNETCORE_FORWARDEDHEADERS_ENABLED=true to my application settings
When I compare fiddler results for the requests, I can see that the AspNetCore.Antiforgery is the same for login and web api calls but the .AspNetCore.Identity.Application value is different.
I am using nSwag to auto generate api service calls for angular if that makes a difference.
QUESTION:
can someone help me figure out why I can login but all web api requests fail with the unauthorized error above?
thanks in advance.
JK
EDIT 1
I used fiddler to get the authorization token for the request and used jwt.io to parse it. The iss value was the same as the app/web api:
"iss": "https://******.azurewebsites.net",
IS4 used this domain to log in and that worked properly. If that value is correct, is there another thing that might be wrong?
EDIT 2:
Just for more context.
My app uses in statup.cs Configure:
app.UseHsts();
app.UseHttpsRedirection();
As a result I needed to add the following code to make sure the headers get forwarded in the requests between app service's handling of the TSL, load balancer/proxy and my docker container (starup.cs ConfigureServices):
// the following is documented here:
// https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1#forward-the-scheme-for-linux-and-non-iis-reverse-proxies-1
// it is needed to run kestrel in azure app service in http with header forwarding
if (string.Equals(
Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"),
"true", StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
}
I get the following error in the logs which confirm the same error above as an Issuer mismatch
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException:
IDX10205: Issuer validation failed. Issuer: '[PII is hidden. For more
details, see https://aka.ms/IdentityModel/PII.]'. Did not match:
validationParameters.ValidIssuer ...
I am using the following default setup for the Jwt token:
services.AddAuthentication().AddIdentityServerJwt();
If i navigate to the https://*******.azurewebsites.net/.well-known/openid-configuration/jwks I get the following JSON setting for my OIDC setup:
{
"issuer": "https://*******.azurewebsites.net",
"jwks_uri": "https://*******.azurewebsites.net/.well-known/openid-configuration/jwks",
"authorization_endpoint": "https://*******.azurewebsites.net/connect/authorize",
"token_endpoint": "https://*******.azurewebsites.net/connect/token",
"userinfo_endpoint": "https://*******.azurewebsites.net/connect/userinfo",
"end_session_endpoint": "https://*******.azurewebsites.net/connect/endsession",
"check_session_iframe": "https://*******.azurewebsites.net/connect/checksession",
"revocation_endpoint": "https://*******.azurewebsites.net/connect/revocation",
"introspection_endpoint": "https://*******.azurewebsites.net/connect/introspect",
"device_authorization_endpoint": "https://*******.azurewebsites.net/connect/deviceauthorization",
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"scopes_supported": [
"openid",
"profile",
"CleanArchitecture.WebUIAPI",
"offline_access"
],
"claims_supported": [
"sub",
"name",
....
"updated_at"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"implicit",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_types_supported": [
"code",
"token",
"id_token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"response_modes_supported": ["form_post", "query", "fragment"],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"id_token_signing_alg_values_supported": ["RS256"],
"subject_types_supported": ["public"],
"code_challenge_methods_supported": ["plain", "S256"],
"request_parameter_supported": true
}
I compared the Issuer in this document and they are the same as the one in the token as shown decoded above.
I am still not sure how to debug this to figure out where the issuer mismatch is happening.
NOTE: I have narrowed this down a bit. All calls to the built in/default IS4 endpoints work. Its only the custom webAPI endpoints I define in my controllers that are not validating the token properly.
Any webAPI endpoint with [Authorize] attribute fails with invalid issuer
EDIT 3:
Thanks to #d_f comment I used the IS4 docs for adding local API
I added the following call to my services initialization in startu.ca configure services:
services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddLocalApiAuthentication(); // I added this line after the above line
I then changed the [Authorize] attribute at the top of my webAPI controller to:
//[Authorize]
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
However, I am still getting the same error. Only on my custom webAPI endpoints, the IS4 endpoints all work. Login works but not any web api endpoints that have [Authorize] attribute.
EDIT 4:
I removed the above settings and chnaged my services.AddAUthentication() to the following:
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi(options =>
options.ExpectedScope = "IdentityServer4");
I also tried:
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi();
I used the policy name "IdentityServer4" because it appears to be a default policy within IS4
Here is what the full context looks like:
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi(options =>
options.ExpectedScope = "IdentityServer4");
This works locally on my machine with all these variations. Its just when run inside container in azure web app that I get the issuer failure for my custom webAPI endpoints.
SOLUTION:
I found a solution thanks to all the help here. IS4 out of the box attempts to set the ISS / Issuer automatically. This works locally but in my production environment my container run in azure web apps for containers. Azure places my container inside of another container for load balancing/proxy to handle the https encryption as well. As a result there is a difference between the auto detected IS4 issuer in my container and the azure web app URL.
By manually setting the issuer in my code the error went away and everything works.
You can do this in two places
in your appsettings.jsson like:
"IdentityServer": {
"IssuerUri": "https://yourapp.azurewebsites.net",
or in code like this:
services.AddIdentityServer(options =>
{
options.IssuerUri = "https://your.azurewebsites.net/";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Hope this helps someone else and thanks again to all who helped here
You need to capture your token and use https://jwt.ms to parse it.
According to your error message: invalid token The issuer is invalid, so you should check the iss Claims in the token to make sure it is as expected in the API The issuer matches. see here.
SOLUTION:
I found a solution thanks to all the help here. IS4 out of the box attempts to set the ISS / Issuer automatically. This works locally but in my production environment my container run in azure web apps for containers. Azure places my container inside of another container for load balancing/proxy to handle the https encryption as well. As a result there is a difference between the auto detected IS4 issuer in my container and the azure web app URL.
By manually setting the issuer in my code the error went away and everything works.
You can do this in two places
in your appsettings.jsson like:
"IdentityServer": {
"IssuerUri": "https://yourapp.azurewebsites.net",
or in code like this:
services.AddIdentityServer(options =>
{
options.IssuerUri = "https://your.azurewebsites.net/";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Hope this helps someone else and thanks again to all who helped here

Loging out of Azure Passport authentication Node js

I have a node js application in which we have used azure login with passport authentication.
I have successfully logged in using azure and the application is working fine.
But, when I logged out and give the url to a page - it checks for authentication and automatically go to that page without asking for login.
Once I logged in my url contains below query string
1. session_state
2. code
3. state
4. token
Log in Code:
app.get('/login', passport.authenticate('azuread-openidconnect', { failureRedirect: '/' }), function (req, res) {
res.sendFile(path.join(__dirname+'/index.html'));
});
Logout code:
app.get('/logout', function (req, res) {
req.session.destroy();
req.logout();
res.redirect('/');
});
When i logout the page redirects to my index page. Then when i give '/login' to the url it takes me to the page without going to logging in page
Please help to get out of this...
This issue is caused by the Authorization Code Grant Flow of OAuth 2.0. Something like that there are any session on Azure AD OAuth 2.0 service. It is not the problem of passportjs or expressjs.
We can have the following simple test, visit the authentication endpoint in browser, https://login.microsoftonline.com/common/oauth2/authorize?response_type=id_token%20code&client_id=<client_id>&redirect_uri=<redirect_uri>&response_mode=query&scope=openid
You will need to fill the email and password first, after you finishing the login flow, the second time you visit the endpoint, you will not longer need to fill the email or password anymore.
We can set the url param prompt to login in the authorize endpoint to force the users to re-authenticate every time.
You can refer https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx#code-snippet-3 for the details.
But in the azure passport oidcstrategy, we should modify the source code for add the param into the endpoint.
After you install the passport-azure-ad module, open the file /node_modules/passport-azure-ad/lib/passport-azure-ad/oidcstrategy.js, at Line 545 (more or less), you can find the following code snippet:
var params = {};
if (self.authorizationParams) { params = self.authorizationParams(options); }
params['response_type'] = config.responseType;
log.info('We are sending the response_type: ', params['response_type']);
params['client_id'] = config.clientID;
params['redirect_uri'] = callbackURL;
...
We can add the sentence params['prompt'] = 'login'; following the code snippet to add the support.
Any further concern, please feel free to let me know.
edit
Is there any way to prompt login only when i logged out...
I am not sure that do you mean, you want to check the user is authenticated when he visit login route, if is, do not prompt login flow?
If so, you can custom a middleware to check the authenticated. E.G.:
function checkAuthenticatedOnLogin(req,res,next){
if (!req.isAuthenticated()) {
return next();
}else{
res.send('do not need login');
}
}
app.get('/login',checkAuthenticatedOnLogin,
passport.authenticate('azuread-openidconnect',{ failureRedirect: '/login' }),
function(req, res) {
log.info('Login was called in the Sample');
res.redirect('/');
});
Logging out of your application does not mean the user is logged out of Azure. When a user logs out of your application you simply destroy the session your application has for said user. When they go to login again it will redirect them to Azure (where they're still logged, and where your application still has permission) which then instantly redirects back to your application with a token for that user. You would need to have the user log out of Azure to have them prompted again for their credentials.
1) I had this same problem, as passpor-azure documented I was executing this function to logout:
logout: function(req, res) {
req.logout();
res.redirect('/');
}
But Azure login session keeps active, so when I re-enter into my website, the autentication request will automatically be valid and no login page will be showned.
I tried #Gary Liu - MSFT suggestion to configure prompt:login option, but has #user3211705 commented, this makes login page reappear (even if I don't do logout and I have a portal.azure.com tab open), for instance when I restart my server (note: this isn't wrong, but when we are developing, one restarts the server all the time, and it gets annoying to login all the time)
2) Part of my solution came from this post:
Which suggest to invoke this url to logout user at Azure AD:
https://login.microsoftonline.com/<tennantid>/oauth2/logout
But only doing this didn't do the job for all situations.
If I have my website open in more than one window/tab browser, the user gets invalid in Azure AD by invoking this url, but I can keep using my website at the other tab (it seems like the user in that tab session keeps still active).
This is similar to have my website in one tab and on another tab with the portal.azure.com open, and do logout in portal.azure.com which invalidate my user at Azure AD.
So my final solution was a mix of both
logout: function(req, res) {
req.logout();
res.redirect('https://login.microsoftonline.com/<tennantid>/oauth2/logout');
}
This does logout my user in the request and invokes logout authentication in Azure AD.
One can still add a redirect uri at logout url param ?post_logout_redirect_uri=
/oauth2/logout?post_logout_redirect_uri=<your website>
Looking at the code for Azure xplat they don't seem to call an explicit logout function, instead they simply delete all of the relevant client side tokens.
If there are no local tokens you can't be logged in!

Google oAuth2 Access Token: "invalid_grant"

Similar question to this, except those answers are outdated and not applicable to my current situation.
Here's my webapp's signin flow:
Log in with Google (forces prompt, gives offline approval)
I successfully get a profile back with an access token and a refresh token
I do a few bits and pieces, then use that refresh token to generate an access token and download the user's contact list
This was working fine (last tested a week ago) but today it's suddenly giving me:
error: invalid_grant
I'm using Node's xoauth2 package, and this hasn't been updated since June so I can't see why this would suddenly be a problem now - unless Google has changed something on their end in the past week or so?
Sample of the code I'm using that calls the error:
// User credentials - all verified working + correct
xoauth2gen = xoauth2.createXOAuth2Generator({
user: email,
clientId: configAuth.googleAuth.clientID,
clientSecret: configAuth.googleAuth.clientSecret,
refreshToken: refresh
});
// SMTP/IMAP
xoauth2gen.getToken(function(err, token){
if(err){
return console.log("XOAUTH2 Error: " + err);
}
if(type === "full"){
cb(token);
}
});
Edit: For completeness, the scopes I'm using when generating the refresh token are:
app.get('/auth/google',
passport.authenticate('google',
{
scope : ['https://mail.google.com/',
'profile',
'email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.google.com/m8/feeds'],
accessType: 'offline',
approvalPrompt: 'force'
}
));
According to RFC 6749 invalid_grant is returned when:
The provided authorization grant (e.g., authorization code, resource
owner credentials) or refresh token is invalid, expired, revoked, does
not match the redirection URI used in the authorization request, or
was issued to another client.
Check this out here
This was eventually fixed by deauthorizing my app in my Gmail account, deleting the refresh token I had stored in MongoDB and starting from scratch. Appears that it was simply a case of some permissions that I had changed, that hadn't been granted via oAuth2.
If you are running a test server using localhost, this error may occur if your system time(OS time) is out of sync.
My system clock was not correct as my CMOS battery was dead. Every time I restarted my laptop the time would not be correct. Hence I would get this error when running
`node server.js'
and then making a request from the frontend to my express js backend

Resources