Microservices and oauth architecture - oauth

I'm learning microservice architecture and I have some questions.
Lets imagine arhitecture like this:
We have simple html pages (example.com): main page, information for developers, login, sign up, etc. Login page (example.com/login) contains a lot of oauth buttons (e.g. facebook, google+, twitter, etc). All of them have client_id, state (generates every request and saves in session) and redirect_url. So, the redirect_url points to another microservice (auth.example.com/oauth?code=...&state=...). Then, I change the code to a token, send request like get_user_info and save it in db (aka registration or update user). After than I generate a JWT token to call my API and return the token to user with redurect to dashboard.example.com. On this page user will use my API (api.example.com) with the JWT token.
Thus, we have 3 microservice: main site (contains info and login) - example.com, auth server (authorize and authentication) - auth.example.com and API service - api.example.com
And questions:
1. How I can check the state when oauth provider redirect it from example.com (this we store the state in session) to auth.example.com? I think use rebbitMQ, it ok?
2. Can I put JWT token in body and send response with HTTP 302 redirect (redirect to dash.boardexample.com)?
I'll be glad to hear your opinions and your advice!
Thanks,
Dima.

Related

Hiding the IP address and port of my Authorization Server

I consider building a web application and I want to use OAuth2 protocol for the authentication and authorization of users.
I know that Client Server redirects user to Authorization Server to enter username and password. Thus, user can see the URL of login page on browser. I do not want to show what is written on url actually to user due to security reasons. I do not want to expose my Authorization Server with an host name and port to public.
As a solution, I decided Client Server act on behalf of user(browser). That means, client server will send get login page request and return the response (html) coming from Authorization Server. This is simple and I am able to serve login page on Client Server, but I do not know how can I make a request model (client-id, client-secret, redirect-uri, username, password) and where I will send request to (for example "/oauth2/authorize" and then "/oauth2/token") manually. I should apply all the steps in the right order. Can you suggest me this approach? or do you have any idea to achieve hiding the Authorization Server from public? or that is not so important?

Pushed Authorization Request lifetime in OpenID Connect

As we can see here: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-par#section-2.2
pushed authorization request lifetime should be between 5 and 600 seconds.
So assume that it's 60 seconds. Then client redirects user to authorization endpoint and... user is not logged in. So authorization endpoint redirect the user to endpoint with login page with request_uri as query param. The user logs in, login page redirect user to authorization endpoint with request_uri from query param. Probably, if lifetime was about 10s the request_uri is expired now (and what's more it's used more than once). So how can we handle the flow when user is not authenticated while he is redirected to authorization endpoint and we use PAR?
I know that can be 600 seconds also, but the recommendations say that this lifetime should be as short as possible. Therefore, it seems to me that I misunderstand how PAR works. I don't think even 10 minutes is enough because what if user currently doesn't have account at the identity provider or there is MFA used?
Please tell me, how PAR really works.
Consider a browser based app implementing this flow:
STEP 1
The browser wants to begin a login and calls its backend. The backend sends a standard Open ID Connect request like this to the authorization server:
POST https://login.example.com/oauth/authorize/par
Authorization: Basic czZCaFo3RmpmcDBa:QnIxS3REUmJuZbUl3
client_id=myclient&
redirect_uri=http%3A%2F%2Fwww.example.com%2F&
scope=openid%20profile&
response_type=code&
response_mode=jwt&
code_challenge=WQ4Y4CQpO8W6VtELopzYHdNg&
code_challenge_method=S256&
state=NFBljlVuB1GDjgGARmqDcxtHhV8
The authorization server saves the OIDC request details. Note also that the authorization header includes the client secret, which is one of the key features of PAR - the client authenticates before the redirect to the authorization server. So a malicious app cannot redirect a user with your client ID, since they do not know the secret.
STEP 2
The browser then uses the request URI. The short time you mention is only for this redirect and does not impact user login time:
https://login.example.com/oauth/authorize?
client_id=myclient&
request_uri=urn:ietf:params:oauth:request_uri:7d353fc8-9b94-488f-8c61-cf7cc1dfef9e"
STEP 3
The user logs in, and in some cases that might take a minute or so, as you say. Then a response is returned to the browser:
https://www.example.com/callback?response=eyJra...
In this example I am receiving the response as a JWT, using a related standard called JARM that can be used in conjunction with PAR. The JWT looks like this and could contain an error response in some cases:
{
"exp": 1629112321,
"iss": "https://login.example.com",
"aud": "myclient",
"iat": 1629112301,
"purpose": "authz_response",
"code": "abcdef",
"state": "12345abcdef"
}
If you don't use JARM you will instead receive code, state and error fields in the browser URL. The flow finishes with the usual authorization code grant POST, to swap the code for tokens.
SUMMARY
All of the above is designed to prevent man in the browser attacks. Eg a malicious party cannot alter any fields in flight. Extra security is therefore added to the standard code flow.

OIDC Azure AD token?

I am trying to configure a third party web application to use Azure AD as the OIDC provider. The authentication works fine, however I am looking for some claims and not able to find an ID or Access Token. Here is the flow as I am seeing it
Call to the login page of the web application. This gets a 302 redirect to the Microsoft OAuth endpoint as below
The URL is https://login.microsoftonline.com/-tenantid-/oauth2/v2.0/authorize?client_id=-clientid-&redirect_uri=-encodedCallbackURI-&response_type=code&scope=openid+email+profile&state=123 This does a 302 to below URL
Next call is to https://login.microsoftonline.com/-tenantid-/oauth2/v2.0/authorize?client_id=-clientid-&redirect_uri=-encodedCallbackURI-&response_type=code&scope=openid+email+profile&state=123&**sso_nonce=O.eyJ0eXAiOiJK......**&client-request-id=-guid-&mscrid=-guid- This returns a 200
Next is the redirect back to the hosted web application indicated in teh callback - https://webApplicationURL/callback?code=0.AQ4Ayjxg80......&state=123&session_state=5b7c2e43-9eab-4bb1-9f24-d020f144d30d
At this point, the user has successfully been authenticated. However, I would like to find the ID or Access Token received.
The sso_nonce(in #3) is in a JWT format but has no claims.
The code(in #4) doesn't have any of the claims either and doesnt really seem to be a JWT token format.
So where is the ID Token or Access Token that I can use to decode and see what claims are getting passed (or not)?
Thanks in advance,
Jake.
To get tokens while calling login page of the web application, you can execute the below request in browser by including response_type as id_token+token:
https://login.microsoftonline.com/<tenant_ID>/oauth2/v2.0/authorize?
client_id=da5daf42-xxxx-xxxx-xxxxxx04a52 //your AppID
&response_type=id_token+token //Required
&redirect_uri=https://jwt.ms //your Redirect URL
&response_mode=fragment
&scope=openid+profile+email
&state=12345
&nonce=678910
Make sure to enable tokens for your web application before executing the above request like below:
Go to Azure Active Directory -> App Registrations -> Your App -> Authentication -> Enable tokens -> Save
I tried to reproduce the same in my environment and got the below results:
When I executed the above-mentioned request in the browser, it asked me to sign in like below:
After successful sign-in, it took me to the redirect URL with tokens in the address bar like below:
When you copy-paste the above in Notepad or any, you can find both access_token and id_token like this:
I got the claims successfully when I decoded the token like below:
Reference:
OpenID Connect (OIDC) | Microsoft Docs

Attempting to connect to a IdentityServer4 login page fails

I have an existing website that I want to do a proof of concept with OAuth2 / OIDC. To this end I've configured a locally running IdentityServer4 MVC app as my demo OIDC server following the IdentityServer4 quick setup guidelines. This works fine and navigating to:
http://localhost:5000/.well-known/openid-configuration
Lets me see the discovery document.
I have created a fake login page on this OIDC app which consists of just a login button with no user credentials required.
There's no actual user database and I'm just hard coding some user details to return when the 'authentication' occurs.
In my pre-existing site I've added the OWIN middle wear and am configuring OIDC using the OpenIdConnectAuthenticationOptions. The clientId, scopes, secret etc all match as required and the authority is set to point to my locally running demo OIDC app (http://localhost:5000). The redirect url is set to return to my pre-existing site once authentication is complete.
This all appears to be fine but here's what I want to achieve and can't get working. On my pre-existing site when I navigate to any page that requires authentication I want the user to be redirected to the login page I created on OIDC app. They click the login button (no user details required) and are authenticated and redirected back to the original page.
Currently when I navigate to a protected page I am successfully redirected to the OIDC app but I am redirected to an error page and I don't know why. The error page gives me no detail, it's actually hard coded in the app.
When I look at the discovery document I see that the setting for the 'authorization_endpoint' is set to:
http://localhost:5000/connect/authorize
So I thought maybe I needed to either change that to point to Home/Login which is where I've created the dummy login form, or else I needed to actually create that connect/authorize endpoint and put my form there. Creating the end point makes no difference, it never gets hit and instead I just get the error page on my OIDC app. Changing it to home/login also appears to be ignored.
I am away from my main PC at the moment hence the lack of code snippets but essentially the set up is as per the IdentityServer4 quick setup guide and the OIDC app does appear to be working.
The issue is getting my pre-existing site to properly redirect to the login page.
I've been stuck on this for quite a while now and would like to even get to the stage of seeing the dummy login page. Any pointers are appreciated and again apologies for the lack of sample code.
UPDATE
I've got the login form appearing by setting the openidconfiguration like so:
Configuration = new OpenIdConnectConfiguration()
{
AuthorizationEndPoint = "http://localhost:5000/home/login"
}
However, this isn't logging me in when I click login. On that login action I'm doing this:
await HttpContext.SignInAsync("subjectId","username", authenticationProps);
And then redirecting back to my existing site. However it's not authenticating me and the redirect ends up being redirected back again to the login page.
UPDATE 2
I think the redirect URI should possibly be doing something more. Currently I do the following:
Try and access a restricted page -> Redirected to OIDC server -> Click Login (this sets the subject and user successfully) -> Am redirected to redirect URI which immediately bounces me back to the OIDC server.
So maybe the redirect URI is supposed to confirm login or otherwise do something?
So in the open id connect protocol, the authorize endpoint is used to validate the client information passed as query parameters (client_id, scopes, redirect_uri, etc). In your authentication server, none of that is being checked if all the endpoint does is return a form. Then again the validation can be tedious so keeping the authorize endpoint separate from the endpoint for logging in might be worth a thought.
The developers of Identity Server thought the same thing which is why they set up the endpoint (and endpoint validation) for you as part of the middleware. The validation uses the components that were injected (primarily the client_store, and your defined scopes) to be used by Identity Server.
After the framework validates your authorize request using your client store implementation, it will redirect the user to whichever login page you specify. The login page can be specified by changing it with the a delegate that can be passed in as the second parameter of 'AddIdentityServer' (that takes in something of type IdentityServerOptions that we'll refer to as just 'options'). In the delegate you can specify the login url of the page by changing the value of 'options.UserInteraction.LoginUrl' to the url of the login page.
After the user logs in and you call the signInAsync method on the HttpContext, you're actually supposed to redirect back to a query parameter passed to the login page referred to as the 'return_url' (which is basically the initial authorize endpoint request). This authorize endpoint further validates the cookie and will send the user back to the 'redirect_uri' (if consent on the client is set to false) with a code (if using the authorization code flow) or the id_token and optionally the access token (if using the implicit flow).
Assuming the implicit flow for simplicity, the tokens can be found in the request to the 'redirect_uri' and from there it's all up to you. Commonly the client will issue some kind of cookie (which can potentially contain the id or access token) to mark the successful authentication by the identity provider.

Am I doing this whole API, client app, Oauth/OpenId Connect thing right?

I have some programming experience, but only with PHP and Java enterprise systems. But now I have some ideas about a web app in my new job. Since I am new at this, I would like to share how I have done the whole API in a server, browser app and authentication with Google’s OpenID Connect (I read a lot about Oauth and OpenID Connect, most helpful source was this: https://developers.google.com/identity/protocols/OpenIDConnect).
Server: Laravel - hxxps://coolapp-api.mycompany.com
Client: Angular - hxxps://coolapp.mycompany.com
TL;DR version:
1) User goes to hxxps://coolapp.mycompany.com, gets an Angular app login page. Types in their email, clicks “Sign in with Google”;
2) The app sends the email to hxxps://coolapp-api.mycompany.com/api/sign-in. The server redirects the user to hxxps://accounts.google.com/o/oauth2/auth with all the needed parameters;
3) The user logs in to their Google account, gives my app permission if it’s their first time, and Google redirects them to my server at hxxps://coolapp-api.mycompany.com/sign-in/google/callback. The server checks everything, and if it’s all correct, it creates a JWT token and send a redirect to the client app at hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN
4) The client app gets the token, stores it in local storage, and sends it to the server with every API call
More detailed version:
1) User goes to hxxps://coolapp.mycompany.com, gets an Angular app login page. Types in their email, clicks “Sign in with Google”;
2) The app sends the email to hxxps://coolapp-api.mycompany.com/api/sign-in. The server creates a state token and stores it in cache, associated with the email received. Then the server creates Google’s oauth URL and sends it to the client in the response body. I tried to do it with a HTTP redirect, but Google’s server was responding with an CORS error. The Angular app reads Google’s url from the response and goes there.
3) The user logs in to their Google account, gives my app permission if it’s their first time, and Google redirects them to my server at hxxps://coolapp-api.mycompany.com/sign-in/google/callback?code=AUTHCODE&otherstuff. The server sends the code it received (and all the other needed parameters) to hxxps://accounts.google.com/o/oauth2/token. It receives a id_token with that user’s email and basic info. This app is not public, so I don’t want anyone with a Google Account logging in, only the clients whose emails I added to the server database. So now the server checks if the user’s email in the token is in the database. If it’s not, it sends the user a HTTP 401 - Unauthorized. Then the server checks the state token in it’s cache associated with the email received. If it’s equal to the one received with Google’s redirect, then the server creates another JWT token, but now signed by my server. Finally, it sends a HTTP redirect to hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN with the new token.
4) The client app gets the token, stores it in local storage, and sends it to the server with every API call
Some comments:
Everything is HTTPS;
I added the strictest CSP policies I could to my Laravel server and Angular client;
Currently the app only supports Google’s sign in, while it is in development. Later on I’ll add more.
I made that my server only checks if the user’s email is in the database after they logged in with google because I like that idea that a non-authorized user should have no information about anything. If I made that check before it, during the first round trip, anyone could type an email and discover if that email has an account in my system;
On the last step, when my server sends the JWT token to my client app, I tried sending the token within a cookie, but since my API and my client app have different domains, my client app couldn't read the token. Sending it in the url was the only solution I could find. I tried logging in a popular app that uses Oauth and they did it this way too.
So my question is:
Am I doing something wrong, unsecure, weird?
Thank you all very much
1) Entering an email address every time a user wants to log in is tedious. And it's not needed if the user is already logged in at Google. The user should just click the "Log in with Google" button and get logged in without entering anything. The state parameter can be a random string - not related to the user's email in any way.
2) If you want your backend to process the redirect from Google (using the auth code flow - the backend has the client role in OAuth2 terms), the backend should also initiate a redirect to Google - not by sending data containing the redirect URL. To achieve it, after clicking the "Log in with Google" button, perform a whole page navigation (instead of an XHR request) to /api/sign-in and if the backend returns HTTP 302, the browser will correctly redirect to Google.
3) You should perform request validation (the state parameter) before getting tokens and checking whether the user exist.
On error (access denied), you can consider redirecting the user to an error page with error details instead of returning HTTP 401, since the HTTP code will cause a generic error screen to be displayed to the user. If you want to keep using HTTP codes, I think HTTP 403 Forbidden would be more appropriate.
4) Consider using sessionStorage instead of the localStorage. The sessionStorage gets cleared after closing a browser/tab and it's not shared among tabs. It makes it safer and it allows users to use different identity in different browser tabs.
The tokens your backend issues, is their validity time limited? Is the user required to get a new token after some (short) time period? If not, valid token vales may stay in the localStorage and browser's page history, which can be a security problem.
You can consider using your own OAuth2 auth server (such as RedHat Keycloak) which would accept Google (and later some other providers) for authentication and it would also issue access tokens accepted by your backend.

Resources