I am using passport-saml to authenticate users via Google IDP(SAML APP)
My SAML Strategy is configured as below
const samlStrategy = new SamlStrategy({
protocol: PROTOCOL,
entryPoint: SSO_URL, // SSO URL (Step 2)
issuer: SP_ENTITY_ID, // Entity ID (Step 4)
path: CALLBACK_PATH, // ACS URL path (Step 4)
cert: IDP_CERT,
logoutUrl: 'https://accounts.google.com/logout',
logoutCallbackUrl: '/signout'
}, function (profile, done) {
done(null, JSON.parse(JSON.stringify(profile)))
})
passport.use(samlStrategy)
Using the Passport SAML Strategy, I am able to login successfully
On Logout, I am logging out of SAML Strategy as below
server.get('/logout', function (req, res) {
try {
req.user.nameID = req.user.nameID;
req.user.nameIDFormat = req.user.nameIDFormat;
samlStrategy.logout(req, function(err, requestUrl){
if(err){
return res.send({ success: false, error: err });
}
req.logout()
req.session=null
req.user=null
return res.redirect(requestUrl);
});
} catch(error) {
return res.send({ success: false, error });
}
})
This is logging me out of all Google accounts that are logged into the browser.
QUESTIONS:
Is there a way to just logout only from the specific Google account that I have used for SAML Strategy?
Logout callback url is also not called
Related
I use sveltekit, supabase with Keycloak. When I attempt to login, it redirects to keycloak authentication page. After I enter username and password, it returns url like this:
http://localhost:5173/?error=server_error&error_description=Unable+to+exchange+external+code%3A+7cfe164c-1fad-48e2-98a9-c59c0d80d43a.96a435ef-b0a3-45da-85fc-8557377a2cdf.a1e9662f-8901-4944-9490-a0674ba33776
I entered correct Client Secret and Realm URL, and it redirect to my realm correctly.
But it occurs error: Unable to Exchange External Code
I use signInwithKeycloak function to login
async function signInWithKeycloak() {
const { data, error } = await supabaseClient.auth.signInWithOAuth({
provider: 'keycloak',
options: {
scopes: 'openid',
},
})
}
I am in a next-js app and my auth token is stored in cookies.
For some raisons i use Swr and Api route to fetch my secured api backend.
i am trying to find a way to put my auth token in all api request.
During login cookie is set
res.setHeader(
'Set-Cookie',
cookie.serialize('token', data.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: data.expires_in, // 1 week
sameSite: 'strict',
path: '/',
}),
);
This is an example of a page using swr fetch
//page/test.ts - example of my test route
const { data, error } = useFetchContent(id);
if (error) {
showError('error');
replace('/');
}
return <DisplayContent content={data} />
This is a swrFetchHook
// fetchContentHook
function useFetchContent(id: string): ContentDetail {
return useSWR<any>(`/api/content/${id}`, fetcherApiRoute);
}
const fetcherApiRoute = (url: string): Promise<any> => {
return axios(url)
.then((r) => r.data)
.catch((err) => {
console.info('error is ', err)
throw err
});
};
export default useFetchContent;
inside api route
export default async (req, res): Promise<ContentDetail> => {
const { id } = req.query;
if (req.method === 'GET') {
const fetchRealApi = await apiAxios(url);
if(fetchRealApi) {
// here depending on result of fetchRealApi i add some other fetch ...
return res.status(200).json({ ...fetchRealApi, complement: comp1 });
}
return res.status(500)
}
return res.status(500).json({ message: 'Unsupported method only GET is allowed' });
};
and finally api axios configuration
const apiAxios = axios.create({
baseURL: '/myBase',
});
apiAxios.interceptors.request.use(
async (req) => {
// HERE i am trying to get token from cookies
// and also HERE if token is expired i am trying to refresh token
config.headers.Authorization = token;
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
return req;
},
(error) => {
return Promise.reject(error);
},
);
export default apiAxios;
I am stuck here because i cant find token during apiAxios.interceptors.request.use...
Did you know what i am doing wrong, and am i on a correct way to handle this behavior ?
To allow sending server cookie to every subsequent request, you need to set withCredentials to true. here is the code.
const apiAxios = axios.create({
baseURL: '/myBase',
withCredentials: true,
});
Nilesh's answer is right if your API is able to authorize requests based on cookies. Also it needs the API to be in the same domain as your frontend app. If you need to send tokens to the API (the one which is in the cookie), then you will need a small backend component often called BFF or Token Handler. It can extract the token from the cookie and put in an Authorization header.
At Curity we've created a sample implementation of such a Token Handler, of which you can inspire: https://github.com/curityio/kong-bff-plugin/ You can also have a look at an overview article of the Token Handler pattern.
I'm using the Ambassador OAuth2 Filter to perform OAuth2 authorization against Keycloak.
For the logout I use the the RP-initiated logout as described in the Docs of Ambassador
The logout works fine. However I could not figure out how to provide the redirect url needed for Keycloak to redirect to the Login page after successfully logged out. As a result the user stays on the blank logout page of keycloak.
The RP-initiated logout looks as follows
const form = document.createElement('form');
form.method = 'post';
form.action = '/.ambassador/oauth2/logout?realm='+realm;
const xsrfInput = document.createElement('input');
xsrfInput.type = 'hidden';
xsrfInput.name = '_xsrf';
xsrfInput.value = getCookie("ambassador_xsrf."+realm);
form.appendChild(xsrfInput);
document.body.appendChild(form);
form.submit();
I expected that Ambassador provides a way to add the redirect url as a query param or something, but I couldn't find a solution.
Are there any suggestions or workarounds?
I found this in the Ambassador documentation that could be overlooked as I did several times:
Ambassador OAuth2 Settings
protectedOrigins: (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (https://) and authority (example.com:1234) parts are used; the path part of the URL is ignored.
You will need to register each origin in protectedOrigins as an authorized callback endpoint with your identity provider. The URL will look like {{ORIGIN}}/.ambassador/oauth2/redirection-endpoint.
So it looks like ambassador hard codes the redirection-endpoint (redirect_uri) that you need add to your OAuth2 client in Keycloak.
I found a solution for that, is not the best solution but you will logout using a button.
async function logout() {
const data = new URLSearchParams("realm=keycloak-oauth2-filter.ambassador")
data.append('_xsrf', getCookie("ambassador_xsrf.keycloak-oauth2-filter.ambassador"));
fetch('/.ambassador/oauth2/logout', {
method: 'POST',
body: data
})
.then(function (response) {
if (response.ok) {
return response.text()
} else {
throw "err";
}
})
.then(function (text) {
console.log(text);
})
.catch(function (err) {
console.log(err);
});
}
I'm building a web application using the MERN stack. I've made a REST API to interact with my front-end the usual way,but I'm not able to figure out the best way to build a secure API and overall security of the application.
So far this is what I've tried
Flow of authenticating a user/request :
1. User enters the credentials
2. Server responds with access_token and refresh_token, caching refresh_token in redis cache with email as key and access_token in localStorage
3. To access a protected route, user sends the request with Bearer {ACCESS_TOKEN} in request header
4. Server checks if the token is valid and not expired, serving the user back with protected resource
5. In case, the access_token is expired (i've kept the token expiry short eg. 5 min), an action is fired from the front-end (using redux actions), a sample request :
POST http://localhost:3000/user/token
Header Bearer {ACCESS_TOKEN}
Body
{
"email" : "user#example.com"
}
Server performs a check in redis cache if the email has refresh_token
If yes, server responds with updated access_token
This is how I'm signing the tokens
const signToken = user => {
let email = user.email;
let name = user.name;
const accessToken = JWT.sign({
iss : process.env.APP_NAME,
sub : user.id,
email : email,
name : name
}, JWT_SECRET, {
expiresIn : 300
});
const refreshToken = JWT.sign({
iss : process.env.APP_NAME,
sub : user.id,
email : email,
name : name
}, REFRESH_TOKEN_SECRET, {
expiresIn : '30d'
});
// Save to redis cache
REDIS_CLIENT.set(email, refreshToken);
return {accessToken, refreshToken};
}
and for getting new access_token
// Refresh token
const refreshToken = (req, res, next) => {
const extractToken = req.headers['authorization'];
const token = extractToken.split(' ')[1];
if (!token) {
return res.status(403).json({
message : 'Unauthorized'
});
}
REDIS_CLIENT.get(req.body.email, (err, cachedToken) => {
if (err) return res.status(500).json({message : 'Something went wrong'});
if (cachedToken == null) {
return res.status(403).json({message : 'Unauthroized'});
} else {
JWT.verify(cachedToken, REFRESH_TOKEN_SECRET, (err, rt) => {
if (err) {
return res.status(500).json({message : 'Something went wrong'})
} else {
// Generate a new access token here
const newAccessToken = JWT.sign({
iss : process.env.APP_NAME,
sub : rt.sub,
email : rt.email,
name : rt.name
}, JWT_SECRET, {
expiresIn : 300
});
return res.json({
"accessToken" : newAccessToken
});
}
});
}
});
}
Is this approach secure? If not, what are some good ways to design the authentication flow if stack is MERN. Upon searching the internet, many have advised to set short expiry in access_token and use the refresh_token to generate new access_token, but I haven't found how to logout user on inactivity? What if attacker gets the expired token and email id, and generates the new access_token? How will be the oAuth integration?
PS : Sorry for putting so much here, came here after a lot of searches on the internet but found nothing much
In the example of oauth2 strategy usage in the Passport's repo, the following function is presented:
passport.use(new OAuth2Strategy({
authorizationURL: 'https://www.example.com/oauth2/authorize',
tokenURL: 'https://www.example.com/oauth2/token',
clientID: EXAMPLE_CLIENT_ID,
clientSecret: EXAMPLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/example/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate({ exampleId: profile.id }, function (err, user) {
return done(err, user);
});
}
));
How does Passport obtains the profile field? is it provided with the token by the oauth endpoint? or does it come from a separate (session-related) request?
When using, for example, the Facebook's oauth API, the user info is loaded automatically with the Passport's Facebook strategy, so I'm trying to figure out how does this happen and how to implement a similar behavior in a custom oauth2 API.
The user profile is typically loaded after the access_token is successfully retrieved:
https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L175
this._oauth2.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: callbackURL },
function(err, accessToken, refreshToken, params) {
if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }
self._loadUserProfile(accessToken, function(err, profile) {
if (err) { return self.error(err); }
The function to actually get the user information is often provided by the specific strategy (e.g. Facebook, Twitter, etc)
In Facebook's implementation:
https://github.com/jaredhanson/passport-facebook/blob/master/lib/strategy.js#L137