Should I move the auth code to Access Token exchange to the API for a Web API/SPA OpenID Connect implementation? - oauth-2.0

I'm trying to get my head around setting up an OpenID Connect server for SSO authentication. I think my basic setup/requirements are pretty standard, but I'm having a little difficulty putting it all together.
The broad setup is a single page application, a web API, and an identity server. The SPA is served from the same domain name as the web API and the ID server is on a different domain, so I might have several SPA/Web API combinations, but of course every case is the same setup (single host with static content and an API). At the moment I'm working with IdentityServer4 to create the identity server; I'm flexible to trying other providers if there's some kind of problem with that one, but so far so good.
My login requirements are also pretty standard I think; I want to have short-lived access tokens and I also want to use refresh tokens to implement a sliding expiration so users don't have to be redirected off of my SPA until they've been inactive for "a while" (however I end up defining that).
After a bit of research, I think what I want is to use the authorization code flow. So generally, the way I thought this would work is:
A user visits the application host (that serves the web API and SPA); the static SPA is served
The SPA loads and determines that there is no access token in local storage. The SPA kicks off the login process by producing a random identifier and storing it in session storage, then navigates the browser to the ID server host
The user authenticates with the ID server host
The ID server hosts redirects to the client and includes in the redirect the random identifier the SPA originally generated along with an authorization code
Upon loading and detecting that it got an access code, the SPA checks session storage for the identifier stored in step 2. Finding it, the SPA calls the web API to exchange the authorization code for an access token
The web API uses a back channel with the ID server to produce an access token and refresh token
The web API stores the refresh token and access token then issues the access token to the client
In all future requests, the client uses the access token with the Web API. When the SPA determines that the access token it has is expired or about to expire, it request a refresh somehow (I'm going to hand-wave the refresh a bit for now)
So I went through the tutorial on the IdentityServer4 site, and to my surprise I ended up in a bit of a different state. It took me a while to work through it; the step I'm talking about if anyone wants to follow along is "Adding a JavaScript Client", but I'd be willing to be the result is common among people implementing OpenID Connect. The resulting flow differed from what I expected starting with step 5; instead of the SPA calling the web API with an authorization code and requesting an access token, the SPA uses CORS and makes a cross-domain request back to the ID server to request the access token. The tutorial didn't really cover refresh tokens all that much (there's other parts of the docs that do, but only briefly), but I think the implication is that if I wanted to use refresh tokens they'd be issued to the client and it would use local storage to store them; then for future refreshes it'd also do a cross-domain request back to the ID server. As a side note, another bit of surprise was that the tutorial has you use PKCE, which on research seems to be unnecessary for a web application; it's somewhat important as including a SHA-2 implementation client-side increases the size of my application by a fair bit.
I believe it is a bad practice to issue a refresh token to a web client and ask it to store it; I'm somewhat vague on the specific vulnerabilities that opens up, but the general idea is that if someone subverts your client somehow, a refresh token is considerably more powerful than a short-lived access token.
So, getting my head around this, I believe the way I originally though this would work was that the web API is the "Relying party" in OAuth 2 parlance, and the tutorial set it up so that the client is the "Relying party". It makes me think that if I want to get a sliding expiration, I have to go past where the tutorial went and move the functionality for token exchange from the client into the web API like I had originally envisioned. It would end up looking a bit like the web API functionally being a proxy for the SPA to exchange the authorization code for an access token.
Ultimately, my question is: am I getting this right? It looks like there are really two different models for implementing OpenID Connect for SPA/API web applications; one where the API is the RP, and another where the SPA is the RP. If you want to use refresh tokens, I think you should go with option 1, but maybe if you care that the API could impersonate the client you'd go with option 2? That still seems like it wouldn't matter to me; that authorization code/access token swap can only be used for a particular application, so it's not like one API could suddenly authenticate as a different backend in that setup. I'm just nervous about going off on my own to structurally alter the setup the tutorial had since this is security-related.
UPDATE
I used the authorization code flow instead of the implicit flow despite the accepted answer, since that's the most recent recommendation of the IETF (see https://datatracker.ietf.org/doc/html/draft-parecki-oauth-browser-based-apps-02#section-4, and a great writeup at https://brockallen.com/2019/01/03/the-state-of-the-implicit-flow-in-oauth2/). I accepted that answer because using a silent refresh via iframe instead of a refresh token seems to be the most standard approach for what I'm trying to do; using that I was able to build a working system that looks like the tutorial. In fact, the client library it recommends (oidc-client) has a built-in function to handle the details. For completeness, what I'm starting off with is this service:
import oidc from "oidc-client";
import Url from "url-parse";
let baseUrl = new Url(window.location.href).set("pathname", "").set("query", "").set("hash", "");
let redirectUrl = (new Url(baseUrl)).set("query", "redirect=fromIdentityProvider");
let silentRedirectUrl = (new Url(baseUrl)).set("pathname", "silent-refresh.html");
let identitySettings = {
authority: "[my application's id server domain]",
client_id: "[my client's id]",
redirect_uri: redirectUrl.toString(),
response_type: "code",
scope: "openid profile [my application's resource name]",
post_logout_redirect_uri: baseUrl,
automaticSilentRenew: true,
silent_redirect_uri: silentRedirectUrl.toString()
};
let userManager = new oidc.UserManager(identitySettings);
let user = null;
export default {
async logIn() {
await userManager.signinRedirect();
},
async isLoggedIn() {
return !!(await this.getAccessToken());
},
async logOut() {
await userManager.signoutRedirect();
},
async getAccessToken() {
user = await userManager.getUser();
return user ? user.access_token : null;
},
async initializeApp() {
let url = new Url(window.location.href, true);
if (url.query && url.query.redirect === "fromIdentityProvider") {
await new oidc.UserManager({
response_mode: "query"
}).signinRedirectCallback();
window.location = "/";
return false;
}
user = await userManager.getUser();
return true;
}
};
Then in my application I call initializeApp when the app starts and getAccessToken before any API calls. I still need to eventually add the ability to automatically redirect on 401 from the API, but that's pretty easy.
To make the silent redirect work, I created silent-redirect.html based on instructions here: https://www.scottbrady91.com/OpenID-Connect/Silent-Refresh-Refreshing-Access-Tokens-when-using-the-Implicit-Flow. I also integrated Google authentication as an external provider and verified that it also works for silent refreshes, so no trade-off there.
To round it out, for me the answer to my original question is basically "no", I don't want to move the exchange step to the backend. I did also decide to use PKCE even though it seems to me like it shouldn't be necessary, it's in the IETF recommendation I mentioned, so I'll stick with that.

There is a special OAuth2 flow for SPAs - the Implicit grant. If you want just an access token, specify &response_type=token when accessing the /auth endpoint. Alternatively, you can ask for an ID token as well with &response_type=token id_token&scope=openid. The SPA gets the token in the redirect URL from the autorization provider (in the hash part #access_token=...) along with its life-time expires_in=.... So the token stays in your browser - the hash part doesn't get sent to the server hosting the SPA files.
Your SPA should validate and keep both values and before the token expiration, it should call the /auth endpoint in an iframe with &prompt=none parameter. If your authorization provider supports Single Sign On (SSO), then you should get a fresh access token without the user noticing it. So it works similarly to a refresh token, without requiring CORS, PKCE or a client secret.
If you wanted to implement some more sophisticated SSO management, take a look at the OpenID Connect Session management RFC.

Related

How does keycloak determine which signature algorithm to use?

I'm writing an application that uses keycloak as its user authentication service. I have normal users, who log in to keycloak from the frontend (web browsers), and service users, who log in from the backend (PHP on IIS). However, when I log in from the backend, keycloak uses HS256 as its signature algorithm for the access token, and thus rejects it for further communication because RS256 is set in the realm and client settings. To get around this issue, I would like to "pretend to be the frontend" to get RS256 signed access tokens for my service users.
For security reasons, I cannot give the HS256 key to the application server, as it's symmetrical and too many people can access the server's code.
I am currently debugging the issue using the same user/pw/client id/grant type both on the frontend and the backend, so that cannot be the issue.
So far I have tried these with no luck:
copying the user agent
copying every single HTTP header (Host, Accept, Content-Type, User-Agent, Accept-Encoding, Connection, even Content-Length is the same as the form data is the same)
double checking if the keycloak login is successful or not - it is, it's just that it uses the wrong signature algorithm
So how does keycloak determine which algorithm to sign tokens with? If it's different from version to version, where should I look in keycloak's code for the answer?
EDIT: clarification of the flow of login and reasons why backend handles it.
If a user logs in, this is what happens:
client --[login data]--> keycloak server
keycloak server --[access and refresh token with direct token granting]--> client
client --[access token]--> app server
(app server validates access token)
app server --[data]--> client
But in some occasions the fifth step's data is the list of users that exist in my realm. The problem with this is that keycloak requires one to have the view-users role to list users, which only exists in the master realm, so I cannot use the logged in user's token to retrieve it.
For this case, I created a special service user in the master realm that has the view-users role, and gets the data like this:
client --[asks for list of users]--> app server
app server --[login data of service user]--> keycloak server
keycloak server --[access token with direct granting]-->app server
app server --[access token]--> keycloak server's get user list API endpoint
(app server filters detailed user data to just a list of usernames)
app server --[list of users]--> client
This makes the the list of usernames effectively public, but all other data remains hidden from the clients - and for security/privacy reasons, I want to keep it this way, so I can't just put the service user's login data in a JS variable on the frontend.
In the latter list, step 4 is the one that fails, as step 3 returns a HS256 signed access token. In the former list, step 2 correctly returns an RS256 signed access token.
Thank you for the clarification. If I may, I will answer your question maybe differently than expected. While you focus on the token signature algorithm, I think there are either mistakes within your OAuth2 flows regarding their usage, or you are facing some misunderstanding.
The fact that both the backend and frontend use "Direct Access Granting" which refers to the OAuth2 flow Resources Owner Credentials Grant is either a false claim or is a mistake in your architecture.
As stated by Keycloak's own documentation (but also slightly differently in official OAuth.2 references):
Resource Owner Password Credentials Grant (Direct Access Grants) ... is used by REST clients that want to obtain a token on behalf of a
user. It is one HTTP POST request that contains the credentials of the
user as well as the id of the client and the client’s secret (if it is
a confidential client). The user’s credentials are sent within form
parameters. The HTTP response contains identity, access, and refresh
tokens.
As far as I can see the application(s) and use case(s) you've described do NOT need this flow.
My proposal
Instead what I'd have seen in your case for flow (1) is Authorization Code flow ...
assuming that "Client" refers to normal users in Browser (redirected to Keycloak auth. from your front app)
and assuming you do not actually need the id and access tokens back in your client, unless you have a valid reasonable reason. As the flows allowing that are considered legacy/deprecated and no more recommended. In this case, we would be speaking of Implicit Flow (and Password Grant flow is also discouraged now).
So I think that the presented exchange (first sequence with points 1 to 5 in your post) is invalid at some point.
For the second flow (backend -> list users), I'd propose two modifications:
Allow users to poll the front end application for the list of users and in turn the front-end will ask the backend to return it. The backend having a service account to a client with view-roles will be able to get the required data:
Client (logged) --> Request list.users to FRONTEND app --> Get list.users from BACKEND app
(<--> Keycloak Server)
<----------------------------------------- Return data.
Use Client Credentials Grant (flow) for Backend <> Keycloak exchanges for this use case. The app will have a service account to which you can assign specific scopes+roles. It will not work on-behalf of any user (even though you could retrieve the original requester another way!) but will do its work in a perfectly safe manner and kept simple. You can even define a specific Client for these exchanges that would be bearer-only.
After all if you go that way you don't have to worry about tokens signature or anything like that. This is handled automatically according to the scheme, flow and parties involved. I believe that by incorrectly making use of the flows you end up having to deal with tricky token issues. According to me that is the root cause and it will be more helpful than focusing on the signature problem. What do you think?
Did I miss something or am I completely wrong...?
You tell me.

Forge Autodesk - 2 legged OAuth

We have an inquiry from our client, to enable Autodesk Model Viewer from within our App. The first step is to implement OAuth2 authentication (2 legged). I followed Forge Autodesk tutorials, but this is completely new to me, and I cannot configure that to work. I do http request (using jQuery) from our App, passing client_id and client_secret, grant_type and a scope. When looking on the developer menu (F12) - I can see that request is hitting their server and returns with the access_token, expire time, authorization "Bearer" with the status 200. So far so good.
I understand that now I need to make a call back to the API and pass this access_token I received. And here is where I lost: console shows me error, Cross origin ... And the success part of http request is not fireing (in http request success part I'm trying to redirect user to the Model Viewer url + access_token we just received). But it is never fires. Was digging forums and tutorials but can't find any good sample or explanation what I'm doing wrong. Below is my code example:
$.post("https://developer.api.autodesk.com/authentication/v1/authenticate",
{
client_id: 'here_is_a_client_id',
client_secret: 'here_is_a_client_secret',
grant_type: 'client_credentials',
scope: 'viewables:read'
},
function(data, status){
console.log("Data: " + data);
window.location.href = 'https://viewer.autodesk.com/id/here_is_a_long_id_number&Authorization=Bearer&' + data;
});
Any help highly appreciated. Also, I was trying to follow Autodesk tutorials using Node.js, but again, after seeing that access_token get back from their server, can't make a callback and attach this access_token to it. New to all these Authorization/Authentication/Tokens so can't figure out the way it works. Thanks in advance.
I could advice you how to avoid this Cross Origin error, but it is critical not to and very dangerous to authorise your application on the client side. Exposing you client secret key will give everyone the right to access your account and spend cloud credits on your behalf. And access all your content. That is the reason you do should not have that approach.
You should never expose the client secret, neither an read/write scoped access token on a client, those should resides on the server, and server only. The only access token you could eventually see on the client should be a viewables:read scoped token only. And even when using a viewables:read token, I prefer to use a proxy instead myself (see here).

Passport & JWT & Google/Facebook Strategy - How do I combine JWT and Google/Facebook Strategy?

This question is for anyone who is familiar with
Node.js
Express
Passport
JWT Authentication with passport (JSON Web Tokens)
Facebook OAuth2.0 OR Google OAuth2.0
I have been doing some online courses and understand how to do the two following things:
Authentication using Passport Local Strategy + JWT Tokens
Authentication using Passport Google/Facebook Strategy + Cookie/sessions.
I am trying to combine the content from these two courses basically. I want to use Google Strategy + JWT Authentication. I want to use JWT instead of cookies because my app is going to be a web/mobile/tablet app, and I need to be accessing the api from different domains.
There are two issues I am having with this:
To kick off the Google/facebook OAuth pipelines, you need to call either '/auth/facebook' or '/auth/google'. Both Oauth flows work basically the same so when I say '/auth/google' from now on, I am referring to either. Now the issue I'm having is: On the client, do I call the '/auth/google' route with a href button link or an axios/ajax call? If I use the href or axios/ajax approach I am still getting problems with both solutions.
The href approach problem:
When I assign an <a> tag with a href to '/auth/google' the authentication works perfectly fine. The user gets pushed through the Google Auth flow, they log in and the '/auth/google/callback' route gets called. The problem I have now is how do I correctly send the JWT token back to the client from '/auth/google/callback'?
After a lot of googling I have seen that people have simply passed the the JWT back to the client from the oauth callback in the redirect query param. For example:
res.redirect(301, `/dashboard?token=${tokenForUser(req.user)}`);
The issue I have with this is that now the the ability to authenticate is saved in my browser history! I could log out (destroying the token saved in localStorage), and then simply look at my browser url history, go back to the url that contains the token in the query param, and I would automatically log in again without having to go through the Google Strategy! This is a huge security flaw and is obviously the incorrect way to approach it.
The axios/ajax approach problem:
Now before I explain the problem with this issue, I know for sure that If I get this working, it will solve all issues I was having with the previous href problem. If I manage to call '/google/auth' from an axios.get() call and receive the JWT in the response body, I will not be sending the token as url param, and it will not get saved in the browser history! Perfect right? well there is still some problems with this approach :(
When try to call axios.get('/auth/google') I get the following error:
How I've tried to solve the problem:
I installed cors to my npm server, and added app.use(cors()); to my index.js.
I took a stab and added "http://localhost:3000" to the "Authorised JavaScript origins" in Google developer console.
Neither of these solutions solved the issue, so now I really feel stuck. I want to use the axios/ajax approach, but I'm not sure how to get past this cors error.
Sorry for such a long message, but I really felt I had to give you all the information in order for you to properly help me.
Thanks again, looking forward to hear from you!
I solved this in this way:
On Front-End (can be mobile app) I made login request to Google (or Facebook) and after the user selected his account and logged in I got back response that contained google auth token and basic user info.
Then I sent that google auth token to backend where my API sent one more request to the Google API to confirm that token. (See step 5)
After successful request comes you get basic user info and e-mail. At this point, you can assume that user login via Google is good since google check returned that it's okay.
Then you just signup or login user with that email and create that JWT token.
Return token to your client and just use it for future requests.
I hope it helps. I implemented this multiple times and it showed like a good solution.
Though there is good answer, I wanted to add more information with example.
Passport's google/facebook strategy is session based, it stores user info in cookie which is not advisable. So we need to disable it first
To disable session we need modify our redirect router. For example if we have redirect path /google/redirect like following, we need to pass { session: false } object as parameter.
router.get('/google/redirect', passport.authenticate('google', { session: false }), (req, res)=> {
console.log(":::::::::: user in the redirect", req.user);
//GENERATE JWT TOKEN USING USER
res.send(TOKEN);
})
So where does this user come from? This user comes from passport's callback function. In the previous snippet we have added passport.authenticate(....) This middlewire initiates passport's google-strategy's callback which deals with the user. For example
passport.use(
new GoogleStrategy({
callbackURL: '/google/redirect',
clientID: YOUR_GOOGLE_CLIENT_ID
clientSecret: YOUR_GOOGLE_SECRET_KEY
},
(accessToken, refreshToken, profile, done)=>{
console.log('passport callback function fired');
// FETCH USER FROM DB, IF DOESN'T EXIST CREATE ONE
done(null, user);
})
)
That's it. We have successfully combined JWT and Google/Facebook Strategy.
The solution I found was to do the OAuth flow in a pop-up (window.open), that makes use of a pre-defined callback to pass the token to the front-end upon successful authentication.
Below are the relevant code samples, taken from this tutorial:
https://www.sitepoint.com/spa-social-login-google-facebook/
Here is the pre-defined callback and initial open method, called from your front-end:
window.authenticateCallback = function(token) {
accessToken = token;
};
window.open('/api/authentication/' + provider + '/start');
And here is what your OAuth Callback URL should return, upon successful authentication (which is the last step/page inside your pop-up):
<!-- src/public/authenticated.html -->
<!DOCTYPE html>
<html>
<head>
<title>Authenticated</title>
</head>
<body>
Authenticated successfully.
<script type="text/javascript">
window.opener.authenticateCallback('{{token}}');
window.close();
</script>
</body>
</html>
Your token would now be available to your front-end's pre-defined callback function, where you could easily save it in localStorage.
I suppose though, you could do the OAuth flow in the same window then (sans pop-up) and return an HTML page (similar to the above) that just saves the token and redirects the user to a dashboard immediately.
If your front-end domain was different from your api/auth server, however, you would probably need to redirect from your api/auth server to your front-end with a single-use, time-sensitive token (generated by your api/auth server), that your front-end could then use to call and receive (with axios) your actual token. This way you wouldn't have that browser history security problem.

Renew a JWT issued by Azure ACS

We have Azure ACS configured to issue JWT that is valid for 15 minutes. Once the user is logged-in to the web application (MVC), the user will use the token to access resources on another server (WebAPI). The WebAPI server would then validate that token.
So, is there any way to renew the JWT somehow without interrupting user's work on the web app? We don't want to popup a window and ask the user to sign in again.
Thanks!
If you are using Active Directory Authentication Library (ADAL) for .NET, then it includes a token cache. Per this blog post:
One last thing I’d highlight at this point is that every time you get a token from the authority ADAL adds it to a local cache. Every subsequent call to AcquireToken will examine the cache, and if a suitable token is present it will be returned right away. If a suitable token cannot be found, but there is enough information for obtaining a new one without repeating the entire authentication process (as it is the case with OAuth2 refresh tokens) ADAL will do so automatically. The cache is fully queryable and can be disabled or substituted with your own implementation, but if you don’t need either you don’t even need to know it’s there: AccessToken will use it transparently.
ADAL.NET is available on Nuget here: https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/
If you aren't using ADAL.NET, provide more info, such as:
What library you are using
What is ACS on top of, AD FS or Azure Active Directory
We use ACS + ADAL and there seems to be no clever way to refresh the token. Even if the ExpiresOn Time on the Token inside the Cache is due the AcquireToken always returns the stale cached token. We cache the token ourself, so this code is only invoked when the ExpiresOn is due.
I ended up with this dirty hack:
var authContext = new AuthenticationContext(ServiceInfo.AcsUrl);
if (authContext.TokenCacheStore.Count > 0)
{
authContext.TokenCacheStore.Remove(authContext.TokenCacheStore.First());
}
result = authContext.AcquireToken(acsRealm, allProviders.First());

How to develop user-authenticated REST service with Azure ACS

I'm developing a REST service that uses MS Azure Access Control Service for authentication. If the examples are any indication, the typical way to secure a REST service this way would be to provide a global username and pw, private key, or X.509 cert for the protected service. However, I want to use the passive user login mechanism on a mobile device with a flow more like the following:
Unauthenticated user attempts to access protected service from app
Mobile app redirects to browser app (or embedded browser)
User selects identity provider to use for login (facebook, google, etc.) from ACS login page
User enters credentials for identity provider
Browser redirects back to app
App somehow gets the SWT token to use with subsequent REST requests.
I'm stuck at about step 5--getting the SWT token, and the existing examples I've found don't seem to address this scenario. In addition, I'm actually trying to build a proof of concept with a desktop client in WPF, which may complicate things. Can anyone suggest a specific tutorial or a path to pursue that uses the per-user authentication vs. per-service? Thanks.
EDIT:
As I'm digging into this deeper, I've realized that the examples posted below (and most others) are based on OAuth WRAP, which has been deprecated in favor of OAuth 2.0. Can anyone suggest a more up to date reference? Googling has turned up http://blogs.msdn.com/b/adventurousidentity/archive/2011/09/18/acs-v2-oauth-2-0-delegation-support-explained.aspx and http://connect.microsoft.com/site1168/Downloads/DownloadDetails.aspx?DownloadID=32719 but they're not the most intuitive.
You should look into the ACS Windows Phone sample:
http://msdn.microsoft.com/en-us/library/gg983271.aspx
Here instead of using Silverlight you will be using WPF. Most of the code should be re-usable. Note that since you are using WPF you will need to register your own object for scripting e.g:
[ComVisibleAttribute(true)]
public class NotifyHandler
{
public void Notify(string notifyString)
{
// Here I have the token.
}
}
this.webBrowser1.ObjectForScripting = new NotifyHandler();
Update:
The sample above uses OAuth Wrap to contact the secured service. If you would like to use OAuth2 you should change the way the "Authorization" header set:
OAuth WRAP case:
WebClient client = new WebClient();
client.Headers["Authorization"] = "OAuth " + _rstrStore.SecurityToken;
OAuth2 case:
WebClient client = new WebClient();
client.Headers["Authorization"] = string.Format("OAuth2 access_token=\"{0}\"", token);
You can use the "Simple Service" sample as a guide to implement your token validation in your REST service:
http://msdn.microsoft.com/en-us/library/gg185911.aspx
Yet if you would like to implement a more complete sample you can look at how CustomerInformationService is protected in the CTP version 1.4:
https://connect.microsoft.com/site1168/Downloads/DownloadDetails.aspx?DownloadID=35417
Take a look at this one:
WPF Application With Live ID, Facebook, Google, Yahoo!, Open ID
http://social.technet.microsoft.com/wiki/contents/articles/4656.aspx

Resources