I am developing an Restful API for client applications which will reach to the resources on the server after the resource owner gives necessary permissions. For achieving this, I am planning to use Spring Oauth2. Currently, I am asking for an auth. code to the auth. server and using it to get a token for reaching the resources of the resource owner. For instance:
1- The client application asks for the scope 'readPhotos' which is mapped to: /api/v1/photos
2- The resource owner enters his credentials on the page that the authorization server shows.
3- Authorization server asks the resource owner if he wants to grant access for his photos.
4- Resource owner approves and authorization code is passed to the client application.
5- After retrieving the code, the client app passes it to the auth. server for the token.
6- Token is received and the client can reach the photos.
However, I want to let the client to specify the subset of resources it wants to ask permission for. For example, lets say the resource owner has "photo1", "photo2", "photo3", "photo4" in the resource server. What is the correct way to make the client application to ask authorization for a subset of resources, such as files "photo1, photo3" but not the whole collection. How to make scope definitions? As the resources will vary for each user (the photo ids will be different for each user), it is not possible to define each resource with a single scope statically using ResourceServerConfigurerAdapter of Spring Oauth2. Currently I am using the following configuration for photo permissions. However, as I mentioned this scope definition is too generic and gives permission for the whole set.
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
http.requestMatchers().antMatchers(HttpMethod.GET, "/api/v1/photos").access(" #oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('readPhotos')")
The access token has scopes and it has an audience (generally speaking). Those are your variables. You would probably have to use scopes that correspond to the resources (but I supppose audience is sort of relevant as well). The tough part is validating the scopes (or audiences) in the Auth server. Really, I wouldn't expect an auth server to be able to know anything about the available resources, so you pretty much have to grant any scope that a client requests, which may defeat the object. So I think the bottom line is you need to ask yourself if the token is really the right place to put that information, unless your example wasn't really realistic, and the real use case is more interesting.
Or you could put the onus of validating the scopes on the user (he has to approve access to individual photos). That works, I guess, for a grant with approvals (e.g. auth code).
Related
Let me clarify my use case:
I have a next.js application which is a plattform for listing real estate objects. I have several api routes which im using inside my next.js app. for example:
/api/createpost ->
Takes informations from my form on my next.js app and creates a database entry to perform a new post
/api/getposts ->
fetching all the real estate posts from my database and displays it
/api/login ->
logs in a user by checking the credentials in the database and sends a jwt
/api/register ->
registers a user by taking the credentials from a form from my next.js app, registering a user and creating an entry in my database
Now in order to secure my apis I want to make sure to check if there is a valid user session if anybody is calling one of the apis (except the register/login api) to get the expected result. Im doing this by calling the /api/login route and getting a valid user session. Until here everything just works fine. Apis like the /api/createpost can only be called if we have a valid user session.
Now I want to create a mobile app and I want to use my api routes from above to provide full functionality in my mobile app too. It should work the same, if i want to call the /api/createpost on my mobileapp for example, i need a valid user session.
But I want to restrict my api by asking for a key in my database which is pointing to my app and saying okay if you call the /api/createpost api, first of all i need to verify that its the mobile app asking. The mobile app will provide the key in the request then.
I didnt try this yet, but it should work i think. Now the big mess: If we call the /api/createpost and the api wants a valid token to check in the database, which will work for the mobile app, because we are giving it a valid token to check in the database, how can we provide a token if we are calling the api from inside our next.js application? Since I have to do the api call clientside, there is no way for me to provide a secret key or something to validate that the call is coming from my next.js application.
If your application is private
(to be used only by you or a few select people)
You can send a private API key over SSL with each request from your application to the server and verify it. Or you can limit your API to only accept requests from certain IPs.
If your application is public
Unfortunately there's no way to determine where the request is coming from, since anything your app can send, an attacker can send it manually.
Think about it, if your app is trying to make a request to your API, any user can intercept this request before its sent out of his/her machine, and send the exact same request from a different app on the same machine.
You might say, well I can encrypt the requests and responses so that they are of no use to the attacker. But such an encryption will require either a key that's already agreed upon, or some way to provide a new key at the beginning of each session.
If the key is already agreed upon, the app must contain it, as you've already guessed in the question, the attacker can retrieve this key no matter how well you try to hide it.
If the encryption key is a new key provided at the beginning of each session, that's almost how SSL works, your browser handles this transaction. Your server sends a public key to your browser to encrypt the requests which the server can then decrypt with a private key. In this case you've circled back to the same problem, how can you verify to whom you give out an encryption key? What would stop an attacker from requesting the encryption key?
There has to be some way you'd be able to design apps that don't require this restriction. I think the question you should be asking isn't how to restrict your api to a certain app, but how to design apps that don't require this restriction.
We might be able to help you out if you could tell us why you need this restriction.
Update
There is actually a way to verify that requests are coming from your app, but not with an api key.
For Webapps
You can use Google's reCAPTCHA to verify a user on your /register and '/login` routes, and provide an access token or start a valid user session on successful captcha response. With reCAPTCHA v3, you could even verify every user action without interrupting the user. This eliminates both the problems I mentioned in my answer above -
You don't have to store an api key into the app/web app.
The request can't be spoofed as it requires human user interaction within your app. The captcha verification success will arrive to your API from Google's reCAPTCHA server, not from your client app. This communication will be authenticated with a pre-mediated private API key shared by Google to you, which works in the same way as to how you authenticate your external domains.
For Android apps
A similar way to achieve the same thing would be via Android SafetyNet Attestation API. This checks the runtime environment and signs the response with a dynamically generated nonce that your app provides the SafetyNet API.
Please read its docs carefully to understand how you could create potential security loopholes and how to avoid them while using this API.
For iOS apps
DeviceCheck works in a similar way, except the device validity is provided to you by Apple server.
Important edit: "secured" is not the right word here! You cannot tell that a request comes from your app just because the domain is yours. The domain name is not a safe information, as it can be altered easily. See #Mythos comments below.
Initial answer:
Web applications access is secured not based on an API key, but based on a whitelist of domains. That's how we achieve security, because only you have access to the domain where you host your own application: so the request has to be coming from an app you own.
If you try some 3rd party services that provides API for web apps, that's often how they'll work: they will let you configure a set of whitelisted domains that can access your data.
If they provide you an API key, this API key is always meant to be used by a server, not a client-only app.
So if I understand you question correctly, you would do like this for each request:
Check the domain. If it's in the whitelist, perfect, you can keep going. This is meant for web apps (look for "CORS").
If not, check for a valid API token in the headers. This is meant for any app that can store this API token securely (another server for instance, or a mobile app in your scenario though I don't know mobile enough to tell how you store such a key)
I am trying to decipher the meaning "scopes" in the following error message from Error: Could not load the default credentials. context: firebase login:ci AND firebase auth:export:
[2021-04-27T20:48:23.188Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
[2021-04-27T20:48:26.208Z] Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (/home/node/.npm-global/lib/node_modules/firebase-tools/node_modules/google-auth-library/build/src/auth/googleauth.js:160:19)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at runNextTicks (internal/process/task_queues.js:66:3)
at listOnTimeout (internal/timers.js:518:9)
at processTimers (internal/timers.js:492:7)
at async GoogleAuth.getClient (/home/node/.npm-global/lib/node_modules/firebase-tools/node_modules/google-auth-library/build/src/auth/googleauth.js:502:17)
at async GoogleAuth.getAccessToken (/home/node/.npm-global/lib/node_modules/firebase-tools/node_modules/google-auth-library/build/src/auth/googleauth.js:524:24)
Error: An unexpected error has occurred.
If I understood what "scopes" are I might be able to figure out where to set them. So far I have only found a place to set roles and permissions in the IAM for my user.
Here are the definitions I could find:
roles & permissions (source)
A role contains a set of permissions that allows you to perform
specific actions on Google Cloud resources. To make permissions
available to members, including users, groups, and service accounts,
you grant roles to the members.
scopes (source, source)
Access scopes are the legacy method of specifying permissions for your
instance. They define the default OAuth scopes used in requests from
the gcloud tool or the client libraries.
The error message arose when I tried to run firebase --debug auth:export using an OAuth token generated by firebase login:ci and applied in my script with firebase use --token. The OAuth token was generated with the Google user id that owns my Firebase project. That user has role "Owner" on the Google Cloud IAM page (i.e. https://console.cloud.google.com/iam-admin/iam).
How do I set the required scopes for my Google user id when the Google Cloud IAM page only allows you to set roles and permissions?
I'm not familiar with Firebase, but I do recognize this:
Error: Could not load the default credentials
This is telling you that no (application) default credentials were found. You can generate default credentials using a Google user account by executing:
gcloud auth application-default login
There's also other options. I wrote a whole blog post on application default credentials that you may find interesting.
To give you an answer to your other question, I'm afraid I'm going to get a bit wordy...
OAuth
Scopes are a term from OAuth; scopes are not specific to GCP or even to Google. OAuth is a protocol for authority delegation and is in wide use all over the internet. Understanding OAuth fully is not trivial.
The "parties" that OAuth concerns itself with are:
resource (the GCP service you're trying to access),
end-user (you, Michael)
authorization server (Google account service) and
client (in this case, the firebase CLI; the program you want to act on your behalf).
The problem that OAuth concerns itself with is the following: You, as the end-user, have the authority to act on the resource. You want a client to be able to act on the resource on your behalf. To do this, you log in with the authorization server, and get a secret token to the client; how to do this specifically is defined in detail in the OAuth spec. The resource recognizes anyone who possesses such a token as "speaking" on behalf of the end-user. By handing the client a token, you're "delegating" your end-user authority to the client.
Scopes
Anyway... Where to scopes come in? In interactions like the above, you do not necessarily want to delegate all the authority that you carry at the resource to the client. Therefore, you can bind a "scope" to the token. The scope tells the resource that the token bearer may not perform everything on behalf of the end-user, but only certain actions. What specific scopes mean is not defined by the OAuth protocol. Scopes are free-form strings, and it's the resource that should know how to interpret them.
IAM
IAM, on the other hand, is what defines the end-user's authority at the resource, that is, it defines what a Google account can do when it comes to handling GCP resources. Naturally, you a Google account cannot delegate authority to a client that it itself does not have, even if it can create tokens with very broad scopes.
If Google receives a request to, say, start a GCE instance, it will first check if the request is signed by a valid, trusted token with sufficient scopes. If the check passes, Google checks if the end-user that's "backing" the token actually has the authority to perform the requested action using IAM.
In your example above: you're the OWNER, so you have plenty of IAM permissions. However, if the token you generated does not include appropriate scopes, that token does not carry that authority, and the requests sent by firebase to GCP will not pass the first check.
Legacy?
The concept of scopes is not legacy, but the way in which GCP uses scopes to regulate processes' permissions is.
In the past, processes on GCP often ran using generic identities with very broad (IAM) permissions, such as the default compute account. The processes' permissions were limited by scoping the tokens generated for them.
IAM is much more powerful and can be defined at a more granular level as scopes. Nowadays we define custom identities with minimal permissions for different processes. Given appropriate IAM permissions, we can simply create tokens with very broad scopes, such as https://www.googleapis.com/auth/cloud-platform.
I have an Angular Single Page Application (SPA) talking to my ASP.NET API.
The API is protected by my own Oauth2 server (IdentityServer4).
One of my customers (let's call him X) wants SSO: Their users on my platform would sign in on their server instead of using the login form in my app that connects to my IdentityServer.
Each customer has its own subdomain for the Angular SPA (e.g. x.myapp.com). Therefore I can easily redirect X's users to their server's authorization page to approve my API, based on the hostname.
However the API itself uses one common hostname for all customers(api.myapp.com). Customers are distinguished by the Origin header of the API call (x.myapp.com) during the login call (and a few other unprotected calls) and by the Bearer token for protected calls to the API.
How does my API introspect the Bearer access token? Who should know which server to query ?
Is it the responsibility of the API server? Or can I tell my IdentityServer about X's oauth2 server ?
X's users would also be defined on my platform since we need specific info (such as config of roles on the platform). My current setup implies specific claims (such as user id) that allow my API to know what the user can do. Obviously, X's server will not provide the same claims. How can I connect the dots ? e.g. get some standard claim from X's server (username, email, whatever) and match it to my list of users.
Note: This question is similar but the answer is not accepted and seems to imply that the provider of both identity servers is the same (not the case here).
Formatting my comments as an answer:
From reading your question it's pretty much clear to me that you could benefit from what is know as Federated identity.
As you said, one of your customer want to achieve SSO - They want users to login using their existing accounts and be able to user your systems normally.
Since you already have an IdentityServer in your domain, what you can do is delegate the login part to the customer's side (whatever they do it). This is illustrated in the Identity server documentation Federation Gateway.
Basically, the approach is that upon hitting the "login" button in your front-end, you would redirect the users to your Identity Server passing some special params (prompt and acr_values for ex) which in turn, tells identity server to redirect the user's to the external Identity provider (the customer's). After a successful login, you have a chance in Identity Server to augment the claims, maybe using something they returned or anything really. Then the process is as normal - you return a JWT Token generated by your Identity Server
The benefit of doing this is:
Your SPA/API doesn't have to change. You will still work with your own bearer tokens and can continue doing authN/AuthZ as before.
You have a chance to add claims that might indicate where this user is coming from if needed
If your customer's server changes, you don't have to worry much, apart from maybe some tweaks related to returned claims
They don't necessarily need to use OpenId/OAuth on their side for this to work
Useful things you probably will need is some params to pass during the call to the authorize endpoint in Identity Server. (acr_values and prompt).
You can also check this in the quickstarts, by looking at Sign-in with external providers (which is pretty similar to what you want)
Now to your individual points:
Your Identity Server should be the "bridge" between you and the customer's "identity provider".
Upon a login from an external provider (X), you need to somehow identify the user on your platform. You could use email or, even better, if X is already using OpenId/OAuth they might give you the sub claim which is the user id on their side. At this point you need some sort of agreement with them otherwise this might be flaky/unreliable for both sides.
In a more "advanced note" you could also add to your tokens some sort of claim that tells you who is the source provider of this user. Here the source provider would be X. This is useful because you might want, for example, configure allowed identity providers in your app, or maybe enable features only for certain providers. Like, ppl logging in with Google might only see certain parts of the app.
So I'm trying to access my own data from an external app via their API. I only need access to my own data. Not trying to receive data from any of my users' accounts, so they don't need to authorize anything. So obviously I need to avoid any redirects (which seems to be the standard process the more I research OAuth...)
The process is hit the /authorize endpoint, which returns a code. Then provide that code in a request to the /token endpoint. Which then allows me to access my account via the API. I'm 95% sure this process is standard for all OAuth, but figured I'd provide details in case it's not.
How do I provide credentials on the back end to get a code to enter into the request for a token, so that all user interaction is negated? The API I'm using forces me to use OAuth.
The oauth2 grant you are describing is called Authorization Code Grant. This way of authentication has been designed so that applications which want to access resources of a user do not have access to the users credentials.
So if you found a way to interact with the user credentials in this grant it would be considered a hack.
If you do not want the individual user to enter the username and password but you want to access the api with a kind of "system account" this is not the oauth grant you should use.
There are multiple grants that would work for you. The question is which are supported by the authorization server and available to you.
Resource Owner Password Credentials Grant
This grant type is suitable for clients capable of obtaining the resource owner's credentials.
However
The resource owner password credentials grant type is suitable in
cases where the resource owner has a trust relationship with the
client, such as the device operating system or a highly privileged application.
It is very likely that this grant type is not avaiable as it could be misused to steal user credentials.
Client credential grant
The client can request an access token using only its client credentials.
How the resources are tied to a client is not part of the oauth specification and therefore provider specific.
If you want to read more about oauth2 here is a good article.
The Oauth 2 grant flow that you're describing is the Authorization Code Grant Flow, like NtFreX's answer says. And like they say, if you have the option of using one of the above two grants with that API, that's the easiest solution.
However, if you don't, there's still a way to avoid "user interaction". It's unclear what you mean by "user interaction", but in the Authorization Code flow, that usually means logging into a web app that calls the API you are trying to authenticate into, and then consenting on the consent page. See https://dev.fitbit.com/docs/oauth2/#authorization-page for an example (I implemented OAuth 2 for Fitbit :)). You'll need to use an automated web browser like Selenium to click the consent button. Then you can capture the code in the response from /authorize and send the code to the access token endpoint.
Reading this question, #Pinpoint's answer and the further discussion on comments, I'm well aware that natively we can't add an identity provider to our apps developed with ASP.NET 5. One possible replacement for the legacy OAuthAuthorizationServerMiddleware is then provided by the AspNet.Security.OpenIdConnect.Server as I've found in many places.
Now, there is one point that I'm still unsure about all this because I'm really not an expert in security, so my knowledge about OAuth is not very deep. My doubt is the following: is it possible to use an external identity provider when using OAuth to protect one RESTful API?
Notice that I'm not talking about adding social login to one website, I'm talking about using one external identity provider in one RESTful API.
My point is, this makes me a little confused yet, because I always thought this should be a concern of my app.
So my question here is: when using OAuth and ASP.NET 5, is it possible to use an external identity provider, other than implementing one? If it is possible, how this works in short? I mean, my app still needs to be able to manage the identities of users, in the sense that it needs to manage claims and so on.
In that case, if it is really possible, how the flow would be? The external identity provider should issue the tokens? But how my app would be able to verify those tokens and manage users identities?
EDIT: One of the reasons I feel unsure about that is that when we use the UseOAuthAuthentication extension method, we set up one callback path which is described as
The request path within the application's base path where the user-agent will be returned. The middleware will process this request when it arrives.
Now, if we are developing a site, then this really does make sense. The person goes there, click a button to login with a provider like Facebook. The user is redirected to Facebook's page and then after he logs in, he is redirected to some page of the site.
On the other hand, with a RESTful API this is meaningless. There is no notion of being redirected.
This makes it seems that the usage of external providers is only for sites and not for RESTful API's. This is the main point of my question.
My doubt is the following: is it possible to use an external identity provider when using OAuth to protect one RESTful API?
Yes, it's definitely possible. This is exactly what you do when you use Azure Active Directory to protect your API endpoints:
app.UseOAuthBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com";
options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt";
});
The next legitimate question is: if you can use the tokens issued by AAD to protect your API, why couldn't you do the same thing with Facebook or Google tokens?
Unlike Facebook or Google, AAD issues completely standardized tokens named JWT tokens that the OAuth2 bearer middleware can "read" and "verify" to determine whether the token is still valid and was really issued for your API (i.e if the audience attached with the token corresponds to your API. You can control this value using the resource parameter when making your authorization request).
You can't do something similar with FB or Google tokens, since they are totally opaque. Actually, it's not really surprising since these tokens have only one objective: allowing you to query FB or Google APIs, not your own ones (these social providers don't allow to set the audience of the access token).
Since you can't read the token yourself, the only option is to ask FB or Google whether it is still valid to make sure your API doesn't accept invalid tokens. That's something you can (easily) do with Facebook as they offer a "token inspection endpoint" you can query for that: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow (see the Inspecting access tokens chapter). This way, you can ensure the token is not expired and determine the user corresponding to the token.
Sadly, this approach has two downsides:
You have to make an extra HTTP call to the Facebook endpoint to validate the access token, which implies caching received tokens to avoid flooding Facebook with too many requests.
As the access token is not issued for your own API, you MUST absolutely ensure that the access token was issued to a client application you fully trust, or it will allow any third party developer to use his own FB/Google tokens with your API without having to request user's consent. This is - obviously - a major security concern.
You can find more information in the last part of this SO answer (it's for Katana and about Dropbox, but you should get the idea): OWIN/OAuth2 3rd party login: Authentication from Client App, Authorization from Web API
So my question here is: when using OAuth and ASP.NET 5, is it possible to use an external identity provider, other than implementing one? If it is possible, how this works in short? I mean, my app still needs to be able to manage the identities of users, in the sense that it needs to manage claims and so on.
In that case, if it is really possible, how the flow would be? The external identity provider should issue the tokens? But how my app would be able to verify those tokens and manage users identities?
To work around the limitations mentioned in the previous part, the best option is - as you've already figured out - to create your own authorization/authentication server. This way, your API doesn't (directly) accept FB or Google tokens but the tokens issued by your own server, that can possibly redirect your users to FB or Google for authentication.
This is exactly what this sample does: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/tree/vNext/samples/Mvc
The user is invited by the client application (Mvc.Client) to authenticate with your authorization server (Mvc.Server) so he can get an access token to later query the API (also in Mvc.Server). For that, the user is redirected to your authorization server, which itself offers you to authenticate with Google or Twitter.
When this external authentication step is done, the user is redirected back to your authorization server (Mvc.Server), where he's asked to give his consent for the client app (Mvc.Client) to access his personal data.
When the consent is given, the user is redirected back to the client application with the access token you can use to query the API endpoint.