-2

I want to allow SSO (single sign-on) via Google, Facebook, or Apple, etc. while having my own User Entity in a database (Spring JPA) for my mobile application project. My User Entity will hold general info (i.e. Email, First/Last, etc.) along with app-related info (i.e. groups, permissions, etc.).

The mobile application front-end is React Native, and my back-end is a Spring app (back-end only) that holds the REST API.

It looks like I would first allow SSO for Google, Facebook, etc. via an OAuth2 Client on the mobile app front-end. The result of the OAuth2 sign-in (a JWT token) would be sent to the back-end (my Spring app), where I would verify the token and authenticate the user (i.e. create new user, or update existing) and grant requests.

This looks doable, however I ran into some issues/questions to actually accomplish this:

  1. How would I verify the user properly via JWT? A JWT has an identity token (which includes credentials I need, i.e. "email", "first/last", etc.) and an access token. Do I need only need the identity token, or both identity and access tokens?

  2. What would I return to the user after verifying & authenticating them via Spring Security? My own JWT, or the JWT (Google/FB/etc.) that the user sent in?

  3. This is passwordless sign-in, so would I need not only my own AuthenticationProvider and UserDetailsService but also a custom Spring Token instead of "UsernamePasswordAuthenticationToken" instead?

I have looked into Spring Security's authentication process and many examples, but they are not specific to what I am trying to accomplish. I've seen some other questions on here and other websites regarding it, but no explicit examples are provided.

If anyone can point me in the right direction, that would be great. Thanks.

  • 1
    Never return jwts to end clients, always cookies since jwts lack important security features that cookies have. I suggest you read up on Spring handles login against google, facebook etc. Spring will get the jwt and then issue a cookie to your client. – Toerktumlare Aug 26 '23 at 07:24
  • Spring can deal with logins with Google/FB etc. with the Spring OAuth2 client dependency, but that is for a web page. I want my backend to be for mobile only right now – Yusuf Khan Aug 26 '23 at 16:09
  • There is no difference between web page and a mobile client. They are both clients and they both do rest calls! And both are vulnerable to the same sort of exploits – Toerktumlare Aug 26 '23 at 16:12
  • In Spring there is. Spring doesn't have direct support for what I am trying to accomplish if my Spring app is back-end only for a mobile app. Similarly to what this other question has asked: https://stackoverflow.com/questions/43733865/mobile-sign-up-with-spring-social?rq=2 – Yusuf Khan Aug 26 '23 at 16:21
  • @Toerktumlare if you have knowledge of documentation or blog posts for authorizing native applications requests with sessions on a backend configured as an OAuth2 client, I'd be grateful that you share it. This would be particularly useful with ReactNative for which the libs for configuring native app as public client are not really maintained and don't even support app links / deep links correctly (have to use custom scheme, which is now discouraged). – ch4mp Aug 26 '23 at 16:32
  • i dont really understand your question but react native themselfes have https://reactnative.dev/docs/security which states where sessions/sensitive information should be stored on mobile applications, other than that any mobile application should be treated like any other client. Mobile clients can easy be reversed to obtain keys and are susceptible to XSS attacks etc. Mobile apps should most preferably use code flow since implicit flow now is deprecated which means using the BFF-pattern, if one wants to specifically use spring social logins, you should treat your client like its a web browser – Toerktumlare Aug 26 '23 at 18:04
  • I think you are missing the question. I am wanting to use Spring Security to validate a JWT token (the credentials) provided by the client after they did a social sign-in client-side (on mobile app) with an OAuth2 client. You answered #2, and I can just use cookies from Spring Security for that. But I still need insight (from you or someone else) on #1 and #2. Thanks. – Yusuf Khan Aug 26 '23 at 18:19
  • Also it seems that JWT is preferred over cookies for mobile applications (React Native). If I generate my own JWT for a user after I authenticate their SSO (Google/FB) JWT, I can then return an access token to the user with my own backend login API. I believe you are misunderstanding JWTs in this context.. the article you linked above doesn't mention anything against JWTs in my use case. – Yusuf Khan Aug 26 '23 at 18:27
  • @Toerktumlare as far as I understand the link you provided, it does not explain how you can have the REST requests to a BFF executed in the same session as the one in which the authorization_code flow is executed. I know the value of the BFF pattern, I just didn't manage to implement it in native mobile applications (using ReactNative, Capacitor or Flutter). – ch4mp Aug 26 '23 at 20:39
  • I'd even add that if you [browse down the page you linked](https://reactnative.dev/docs/security#oauth2-and-redirects) the authors are talking about a mobile app configured as a "public" OAuth2 client and using authorization_code with PKCE to **fetch tokens**, not of a mobile frontend (not OAuth2) talking to an OAuth2 "confidential" client backend, authorizing requests with session (like I'd like to achieve, with tokens staying on the server). – ch4mp Aug 26 '23 at 21:02
  • @Toerktumlare I should have phrased my questions differently: Have you ever implemented the pattern you promote with a native mobile app? Can you link a sample (I haven't found a single one so far, all are configuring native app as "public" OAuth2 client) or explain in details how it works? But nevermind, thinking of it again, I believe I found the solution: I was deep-linking back into the app too late (after the client have fetched tokens). – ch4mp Aug 26 '23 at 23:52
  • This question is a bit broad. If you have multiple questions in one, try to focus on one question to fix this question. – Blue Robin Aug 29 '23 at 14:33

1 Answers1

0

It should actually be possible to have a mobile application querying a backend configured as an OAuth2 client (Spring or not).

An important thing to understand beforehand is that requests to an OAuth2 client are authorized with sessions, not tokens (JWT or not). Such a backend is stateful and vulnerable to CSRF attacks. You'll have to configure your backend to expose a CSRF cookie and manually extract its value in your mobile app to set it as a header (usually X-XSRF-TOKEN) to each of your POST, PUT and DELETE requests).

Sample authorization_code flow in a web app as you already got working

  1. the user is redirected to an URI on the client responsible for initiating the flow. Let's say https://bff.demo.c4-soft.com/oauth2/authorization/keycloak-confidential-user
  2. what he gets as response is a redirection to the authorization endpoint on the authorization server. Something like: https://oidc.c4-soft.com/auth/realms/spring-addons/protocol/openid-connect/auth?response_type=code&client_id=spring-addons-bff&scope=openid%20profile%20email%20offline_access%20roles&state=nToh3rnFS-tLVTJ7ajWkq-Fbmi0fUhicoS1nxbdCh8I%3D&redirect_uri=https://bff.demo.c4-soft.com/login/oauth2/code/keycloak-confidential-user&nonce=sNWSLXrLBOfb77jZtImq3k0gV4cg_qKoFLiaEWgbsio. This URI must be followed with a user agent capable of displaying login UI (login and password, but also maybe multi-factor authentication tokens, etc.)
  3. the user is redirected back to the client using the redirect_uri passed as query parameter (after having a input credentials if he doesn't have a session opened on the authorization server for the client-id and the user-agent used). Let's say https://bff.demo.c4-soft.com/login/oauth2/code/keycloak-confidential-user?state=ZmaSZzNr5WQ8BjyOAMHtnC8SMty1kEMHuQSCLJO4NEE%3D&session_state=1ec90be5-1388-44ac-8b6c-39910558c037&code=c067d4d5-c3eb-4277-9776-9b5c2509b52a.1ec90be5-1388-44ac-8b6c-39910558c037.fb507d1e-2f06-425c-8885-5f232dc02b4e
  4. the client calls the authorization server endpoint to exchange authorization code for tokens (directly, this does not goes through user device and should require both client-id and client-secret), and stores it in session.

What changes in a native app?

If you redirect the user to the authorization server authorization endpoint with the system browser (or a web view) and let the authorization code flow go to its end normally, the user identity on the backend will be tied to the session for this user agent, not the REST client in your app, and REST requests from your app will remain unauthorized.

The option I just thought of is to modify the request at step 2. above to replace the redirect_uri with a deep-link to your app. When entering in the app back from authorization server with authorization code, extract the parameters, and follow the redirection as it was before you modify it, but using your native app REST client (not the system browser or a webview). Then, let the flow end normally. This should be enough for the tokens to be tied to your native app REST client session.

Why you should probably not do exactly like that

As stated above, OAuth2 clients are stateful. This poorly scales. Also, you'll run into problems as soon as you leave the single monolithic app model (if you adopt a multi-service architecture).

REST APIs are much better configured as OAuth2 resource servers.

The trick is that some of the social identity providers you list deliver "opaque" access tokens, not JWTs, and introspecting tokens on an authorization server standing in another data center is hardly acceptable. This is the case of Google for instance.

According to OpenID spec, only ID token have to be JWTs. It is safe to send ID tokens around, including to frontends, but the reason for this safety is it should only be used to read user info, not to authorize requests (anyone accessing the ID token can read the userinfo, but shouldn't be able to do anything on behalf of that user using ID tokens).

The audience of the access tokens (by whom it is intended to be used) delivered by each of the providers you list are their own APIs (Google access token for Google APIs, Github access token for Github API, etc.). Not your REST API.

If you want tokens to query your own resource servers, you should configure an authorization server of your own. Choose one with identity federation features ("login with ..."). Keycloak is a famous sample you can install on your desktop and servers. Auth0, Okta and Amazon Cognito are just a few samples among many cloud offers. You could even build your own with spring-authorization-server.

You then use this authorization server as only reference in your system: resource servers and whatever are your OAuth2 clients (mobile apps configured as "public" clients or Backenf For Frontend configured as "confidential" client (as discussed in the comments to your question).

spring-cloud-gateway can be configured as BFF with spring-boot-starter-oauth2-client and TokenRelay= filter. This gateway will be stateful, but it is a thin layer only responsible for routing and storing OAuth2 tokens. Most part of the processing will be done by stateless resource server behind it.

ch4mp
  • 6,622
  • 6
  • 29
  • 49
  • I see.. but I have my own database table for a User that I want to use. So you are telling me if I want a user to Single Sign On with Google/FB/etc in my mobile app and use that to authenticate with my Spring REST API, I would need to make my own authorization server? I don't necessarily want to make my own auth/resources servers, I just want to enable Single Sign On with my mobile app so they can use my REST API. This stuff is confusing for me because of how complicated Spring Security is to learn as a beginner so I am trying my best to understand. – Yusuf Khan Aug 26 '23 at 21:33
  • 1
    The complexity you complain about is not related to Spring. This is just how OAuth2 / OpenID works. If you want SSO with the OpenID providers you link, you have no other choice than understanding OAuth2: what the responsibility of each actors are (client, resource server, authorization server and resource owner) and how the flows work (a minimum of `authorization_code`, `refresh_token` and `client_credentials`) – ch4mp Aug 26 '23 at 22:25
  • Right. But what I mean is, implementing OAuth 2 SSO on Spring for web clients is easy. I just implement the OAuth2 Client dependency then throw in my Client ID and Client Secret. So in better words, how can I mimic this behavior for a React Native mobile application - to my understanding that would not require an Auth or Resource server; as Spring OAuth2 Client works for the web app side without that. I'll look more into understanding OAuth 2 and how it works, I feel like I'm overthinking this all. – Yusuf Khan Aug 27 '23 at 00:53
  • See my updated answer. – ch4mp Aug 27 '23 at 05:09
  • I see, thanks for the insight. I used Amazon Cognito before but with AWS-Amplify and ditched it because AWS-Amplify is very limited (which is why I am making my own mobile backend in Spring). If I use Keycloak, would I be able to easily link sign-ins (through Google, FB, Apple, etc.) to my own database? – Yusuf Khan Aug 27 '23 at 17:48
  • Okay after some research and re-reading what you said it makes sense. My Spring backend for my mobile app is the resource server. I would make an authorization server (Keycloak, Okta, etc.) where I have federated identity providers set up (Google, etc.). Then my OAuth2 client (mobile and web frontend) would get a token from the auth server (by signing in), then grant me an access token to then use for GET/POST calls to my resource server (Spring backend). Am I understanding it correctly now? Only question is how I would add these users to my own DB – Yusuf Khan Aug 27 '23 at 18:55
  • This is the option where the mobile app is a public OAuth2 client. It has the vulnerabilities of public clients and exposes tokens to the internet. The other option with a BFF between the front-ends (mobile app or web app) and your resource server(s) has none of this vulnerabilities. For the other point, most OpenID Providers let you customise the claims of access token => you should be able to have it carry the user data you need. – ch4mp Aug 27 '23 at 19:19
  • Makes sense. I am going to implement the public client first then transition to BFF after just so I can get things set up. I am going to store the user from the auth ID after authentication so I can do my own things as well such as linking them to groups, access levels (admin, user, etc.) and so on. Thanks for the help – Yusuf Khan Aug 27 '23 at 20:46
  • This groups definition should be in the authorization server and assignment to user included in tokens. Whatever the authorization server you choose, it should have a REST API for your own resource servers to manipulate user data (groups definition, assignment to users, user profiles updates etc.) – ch4mp Aug 27 '23 at 20:55
  • I noticed that - however for my API and my mobile app to function the way I want, I need my own database tables for groups and other user-to-user relations, access levels, and so on that tie in with my already existing data that persists in my REST API. – Yusuf Khan Aug 27 '23 at 21:42
  • In an ideal scenario, I want have my own relations between the User and multiple things such as Group <-> User (where Group ties into other data tables I have), User -> other User, and other relations. I don't think these platforms are that flexible to support that. – Yusuf Khan Aug 27 '23 at 21:53
  • Think again. Maybe consider that **1)** you don't necessarily need all data in the same database (you should be able to find a unique key for users) and **2)** some authorization servers can be backed by your existing users database (Keycloak and spring-authorization-server are samples). But this are completely different question. I think you got more than enough useful content and the answer to several questions. I won't architect your information system. – ch4mp Aug 27 '23 at 22:04
  • I think you misunderstood what I said. I was implying I would use the unique ID provided by the third party authorization server (Okta/Keycloak) to store in my own relational database. Anyways, I was not asking for you to "architect" anything for me. I was simply responding to your comment. Thanks for the help and have a nice day. Goodbye – Yusuf Khan Aug 27 '23 at 22:10