Oidc client js , don't use Date.now in client - identityserver4

I'm using oidc-client-js in my client for one of my SPA projects.
I have an identity server which is written in IdentityServer4.
If I change date-time of server manually, the oidc-client-js can't validate response of server in log in user, because the date times are not the same.
And also if I change date-time of client manually and keep server with auto date time option, again the response from server is not valid.
I think any JavaScript solution for working with date-time, is not reliable and all date-times must be validated in server.
How can I validate token in server not in client?
Is my assumption is correct ?
And if its not correct, is there any solution for oidc-client-js to use server time instead of browser time?
This is my client configuration
const userManagerConfig = {
client_id: '58bdb6b3dd264200a1186573a8abf884',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/authentication/callback`,
response_type: 'code',
post_logout_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}`,
scope: 'openid profile phone tes.api',
authority: `http://localhost:5016`,
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/authentication/silent_callback`,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true,
triggerAuthFlow : true
};

The time has to be correct. On Server or on Client, it does not matter, but it has to be the actual time. If you temper with the time, the claims on the token (iat, nbf, etc.) may represent a moment on the past or the future so it won't work.
Why would you change the date/time of the server or the client to an invalid one?
How can I validate token in server not in client?
On SPAs everything oidc-related happens in the client code, validation of the token included.
This is not the only, nor the most important, validation that the client does on the token, the main validation is checking that is signed with the pair of the public key exposed by the authority.
I think any JavaScript solution for working with date-time, is not reliable and all date-times must be validated in server.
Why trusting the clock on the server more than the clock on the client machine, what about desktop applications, what about offline...

Related

Azure Authentication - Access Token returning wrong AUD(00000003-0000-0000-c000-000000000000)

I'm trying to authenticate with API Management in Azure through OAuth. I've set up that piece fine.
However from the response, the aud(00000003-0000-0000-c000-000000000000) is invalid from the access token.
Any suggestions/ideas to get the accurate aud in access_token.
I tried to reproduce the same in my environment and got the results like below:
I generated the access token with the same aud as you and got the validation error:
I agree with juunas, To authenticate with API Management in Azure through OAuth, make sure to pass the scope while generating the access token.
I created an Azure AD Application, exposed an API and added scope like below:
Added API permissions like below:
To resolve the error, make sure to pass scope as api://AppID/.default.
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:api://ee1782a6-a994-4013-a396-XXXXX/.default
grant_type:client_credentials
A valid access token to access APIM will be generated like below:
To pass the particular scope from react app using MSAL you can make refer the below sample code:
auth: {
authority: "https://login.microsoftonline.com/common",
clientId: "ClientID",
postLogoutRedirectUri: RedirectURI
redirectUri: RedirectURI
validateAuthority: true,
navigateToLoginRequestUrl: true,
},
cache:
{ cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true,
},
},
{
scopes: ['api://clientid/.default']
},
LoginType.Redirect
References:
OAuth 2.0 Authorisation with the Client Credentials Flow on Azure API Management by Paco de la Cruz
Connect React App with Azure AD using react msal by Ray
You have mistaken the values.
TL;DR: ignore "access token", obtain and read "id token" and verify that "aud" field is your client ID.
First you might obtain a single-use access code (likely something like 0.ABC). Optionally you could fetch open id token. "scope" must include "openid"
Then you can fetch actual open id token using the single-use code. "scope" must be "openid" again. Response might include:
access token - which can be anything including random number of characters, string, your full details or an JWT; I believe that Microsoft returns JWT which is meant to the "00000003-0000-0000-c000-000000000000" audience (meaning "only 00000003-0000-0000-c000-000000000000 can use it - ignore if you are NOT the one")
id token - which is an JWT and should contain your application ID (client ID) in the "aud" field
Always check the "aud" as this says who is the token created for. If it is not you - the token is not for you.

Several Audiences in JWT token to authenticate with azure AD

Requirements
I want to authenticate against Azure AD, (but later deployments can authenticate against any openID connect server)
I am using oidc-client-ts (I don't want to use anything else as I want to be able to authenticate against any Oidc server)
I am using code flow (not really relevant in this question)
I want my SPA to get a JWT token that permits to make graphApi calls (to get the profile of the user)
And of course I want my SPA to access my API server
This means the JWT token must be accepted by 2 audiences: GraphAPI, my API server
First attempt
Here are the settings I am giving to my UserManager:
const oidcSetups = {
authority: buildUrl(config.oAuthAuthority, config),
automaticSilentRenew: false,
client_id: config.oAuthClientId,
loadUserInfo: true,
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}`,
response_type: 'code',
scope: 'openid profile email'
};
The token given by azure ad permits me to get the profile (make a graphAPI call) because it has the audience claim set to target GraphAPI, but it doesn't have the audience claim for my API serveur.
Second attempt
Then, I tried this setting (the change is in the scope):
const oidcSetups = {
authority: buildUrl(config.oAuthAuthority, config),
automaticSilentRenew: false,
client_id: config.oAuthClientId,
loadUserInfo: true,
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}`,
response_type: 'code',
scope: 'openid profile email api://mycompany.pms/mycompany.api.read'
};
Then my JWT token has indeed the audience claim that is realated to api://mycompany.pms/mycompany.api.read. So this is what I want, except that GraphAPI refuses requests as it doesn't have the audience claim that corresponds to GraphAPI.
Question
How can I manage to get a JWT token that permits me to be accepted by several targets/audiences?
As I believe it is not possible to issue a token that target several audiences,what is the way for to access 2 different audiences with the user authenticating only once?
I struggled with this a couple of years ago and wrote this blog post. Microsoft use resource indicators for their own APIs, so you need to handle the MS APIs differently to your own custom APIs. This is the real issue here I think, rather than audience configuration.
Your SPA scopes look fine, and standards based, but I think you'll need to get Graph data via one of your APIs. This requires a different access token, which an API can retrieve via user assertions (on-behalf-of flow), and use of a client secret.

PassportJS not deserializing user on Heroku server

I'm working on a project which is using PassportJS Google OAuth 2.0. When I test on my local machine (with a React client on localhost:3000 and a Express server on localhost:4000), the flow works fine. I am able to send requests to the server and deserialize the user on each request. However, when I host the client on Google Firebase Hosting and the server on Heroku, the user no longer get deserialized on each request.
Here are some specifics of the things I've done / tried / worked locally along with extra information:
The client and server and hosted on different domains.
I am using axios to send the request to the server. In the request, I make sure to set the "withCredentials" property in the options to true to make sure the cookies connected to that domain are sent on each request.
On the server I have CORS enabled for the domain the client is hosted on (as it is currently being hosted on a different domain) and I have "credentials" set to true to allow the credentials to be sent and received.
Please let me know if I've forgotten to include something in the post or if any extra information would be helpful. Thank you in advance.
I don't know if you fix this, but I got the exact same problem, in my case I added sameSite: "none" in my express session setting, it worked.
cookie: {
sameSite: "none", //add this line
...
},

React SignalR - Do not send bearer token in URL

I am writing an application which has just failed pen testing for the following:
Authorisation Token is being sent in the URL:
https://domain/Hub?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Imh1Tjk1SXZQZmVocTM0R3pCRFoxR1hHaXJuTSIsImtpZCI6Imh1Tjk1SXZQZmVocTM0R3pCRFoxR1hHaXJuTSJ9.....
This is happening automatically when sending to the Hub which uses Azure AD authorisation.
constructor (hub: string) {
this.hubName = hub;
this.hub = new HubConnectionBuilder()
.configureLogging(LogLevel.Critical)
.withUrl(`${this.hubURL}${hub}` , {
skipNegotiation: true,
transport: HttpTransportType.WebSockets,
accessTokenFactory: () => {
return `${getToken()}`
}
})
.build();
}
I've scoured for documentation, however I was wondering if there was a way to connect and send requests without exposing the bearer token within the URL?
From the documentation
When using WebSockets or Server-Sent Events, the browser client sends
the access token in the query string. Receiving the access token via
query string is generally secure as using the standard Authorization
header. Always use HTTPS to ensure a secure end-to-end connection
between the client and the server. Many web servers log the URL for
each request, including the query string. Logging the URLs may log the
access token. ASP.NET Core logs the URL for each request by default,
which will include the query string. For example:
And from this documentation
In standard web APIs, bearer tokens are sent in an HTTP header.
However, SignalR is unable to set these headers in browsers when using
some transports. When using WebSockets and Server-Sent Events, the
token is transmitted as a query string parameter.
Seems to me you could disable WebSockets and Server-Sent events. See this question on how to remove WebSockets or Server-Sent events. But then you fallback to long polling or forever frame, and you may not want that.
Since your URL in your question is https I wouldn't bother that much if you have disabled the request logging.
Changing the log level for Microsoft.AspNetCore.Hosting could be done in your appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore.Hosting": "Warning"
}
}
}

Pass custom HTTP header to IdentityServer 4 from oidc-client-js

I am in the process of setting up a solution with an ASP.NET Core WebApi, a Javascript client (with oidc-client-js) and an IdentityServer4 with Identity.
I have followed different tutorials and guides and have come to a solution that works well.
When accessing the JS client while not authenticated, I am redirected to the IdentityServer, where I can successfully log in and am redirected back to the JS client with my bearer token, which I then use to consume the API.
The system will have to support multiple "tenants", all sharing the same DB though.
The idea is to access the JS client with a tenant key in the URL and pass it on:
www.mydomain.com/{tenantKey}/someSubPage
I would like to read tenantKey and pass it IdentityServer using a custom HTTP header like X-My-Tenant-Key. IdentityServer should then include this key in the authorization process.
I have checked oidc-client-js' GitHub page and done some further research, however I was not able to find out how this could work.
The alternative would be to include the key in IdentityServer's URL and apply some MVC routing magic, or to somehow do some dirty stuff with the redirect_uri.
Before trying any of this, though, I wanted to see if I might be missing something here.
This is how my JS client prototype handles it right now:
// Setup
var config = {
authority: "http://localhost:50000",
client_id: "myClient",
redirect_uri: "http://localhost:65000/callback.html",
response_type: "id_token token",
scope: "openid profile email myApi",
post_logout_redirect_uri: "http://localhost:65000/index.html",
};
var mgr = new Oidc.UserManager(config);
...
// Signing in
mgr.signinRedirect();
Don't know if this is what you are looking for, but you can pass the Tenant Key as query parameter through "acr_values" from your client to Identity Server(Authorization Server). This is meant for situation like yours. You need to add acr_values to your client :
config = {
authority: "http://localhost:50000",
client_id: "myClient",
redirect_uri: "http://localhost:65000/callback.html",
response_type: "id_token token",
scope: "openid profile email myApi",
post_logout_redirect_uri: "http://localhost:65000/index.html",
acr_values : "tenant:your_tenant" };
Then you can access the values in your Authorization Server through Authorization Context for example :
string tenant = context.Tenant;
You can read the docs, one of its use is exactly for passing tenant information.

Resources