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

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.

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.

MSAL.js - Implicit Authentication - Issue with reply url parameters

I am using MSAL.js for a javascript based single page application.
We have configure Azure Ad for implicit authentication.
The redirection url also has been configured to a page.
However we have a requirement where we get a daily email report. This email has links to the application page but with query parameters that allows to filter the report for that day.
Now if we access the app via the landing page, we are able to login fine. But with the query params it gives the exception "The reply URL specified in the request does not match the reply URLs..".
I am using the endpoint V2 on Azure AD , hence not able to put * in the reply url. I guess this is an existing problem. But is there some way to overcome the issue of logging in & redirection with parameters?
Thanks,
Ash
The approach we have taken in general with our apps is to specify a specific URL to MSAL as the redirect URL, and redirect to the "local URL" from there.
So when you send a user to authenticate, you store the current path somewhere like in local/session storage.
Then when the user hits that callback page, you can load up the path from storage and redirect the user there.
This way you can avoid wildcard usage, and still get the user where they wanted to go.
You can tell MSAL not to redirect to the current URL and specify the redirect URL like this:
const app = new UserAgentApplication({
auth: {
// Other settings left out for brevity
navigateToLoginRequestUrl: false,
redirectUri: window.location.origin + '/auth-callback'
}
});
In Case of SPA with login redirect at landing on page with querystring parameter you can use the following configuration to preserve querystring parameter after login (MSAL 2.0)
export const msalConfig = {
auth: {
clientId: "YOUR_CLIENT_ID",
authority: "YOUR_AUTHORITY_URL",
redirectUri: window.location.origin
}

MVC with Angular - ValidateAntiForgeryToken fails after Azure Login redirect with Adal

I have an MVC site with an embedded angular client and I've recently implemented an anti forgery XSRF token as a security measure.
I have set it up in Startup.cs as follows:
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
app.Use(next => context =>
{
if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// We can send the request token as a JavaScript-readable cookie, and Angular will use it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
And I've implemented it within my angular front-end like so:
{ provide: XSRFStrategy, useFactory: xsrfFactory}
export function xsrfFactory(): CookieXSRFStrategy {
return new CookieXSRFStrategy('XSRF-TOKEN', 'X-XSRF-TOKEN');
}
And protecting my controllers like:
[Authorize] //Validation of AzureAD Bearer Token.
[ValidateAntiForgeryToken]
public class UserController : Controller
It is intended that the X-XSRF-TOKEN header be validated with any call to my API, and this works successfully for all calls in the original session. However, my app uses Adal to log the user in, and after the redirect from a successful login, this validation step fails and I receive a 400 from my API for any subsequent calls.
The original X-XSRF-TOKEN header is still sent with all outgoing requests from my angular client after the login so I suspect it must be that my server side no longer has the token to validate against, or my server has generated a new one and my client doesn't retrieve it. But for whatever reason it breaks down and it's very hard to debug without creating some custom filter so I can see what's going on inside it.
Is there a way to reset this token after a client side redirect so that both my server and client share common knowledge of it again? Or should I be generating the token in my Index.html for example?
EDIT
Edited controller decoration above for missing [Authorize] attribute.
So my controller is protected by a step validating the AzureAD Bearer token as well as the Anti-Forgery validation. Removing the AzureAD Validation as a test did not resolve the issue, oddly.
Error on failing API calls displays in output after Adal login as:
The provided anti-forgery token was meant for a different claims-based user than the current user.
Based on my understanding, you were protecting the controller using token. For this issue is expected, you can refer the progress of validate the anti-XSRF tokens from below(refer this link):
To validate the incoming anti-XSRF tokens, the developer includes a ValidateAntiForgeryToken attribute on her MVC action or controller, or she calls #AntiForgery.Validate() from her Razor page. The runtime will perform the following steps:
The incoming session token and field token are read and the anti-XSRF token extracted from each. The anti-XSRF tokens must be identical per step (2) in the generation routine.
If the current user is authenticated, her username is compared with the username stored in the field token. The usernames must match.
If an IAntiForgeryAdditionalDataProvider is configured, the runtime calls its ValidateAdditionalData method. The method must return the Boolean value true.
Since you were developing the SPA application with back-end web API, when the request to the web API, it will always issue the anti-XSRF token with no identity. And when you send the request to the back-end with anti-XSRF and Azure AD token, this time the web API already authenticate the request via the Azure AD token. And it will always return false when checking anti-XSRF token to match the identity information.
In this scenario, if the back-end only using the bear token authentication and store the token with session storage, there is no need to enable XSRF prevention since others is not able to steal the token and forge the request.
If your back-end also support the cookie authentication or basic auth, NTLM etc, you can disable the identity checking by adding the following to your Application_Start method: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.(refer this link)
More detail about XSRF/CSRF abouth oauth and web API, you can refer the threads below:
How does ValidateAntiForgeryToken fit with Web APIs that can be accessed via web or native app?
AntiForgeryToken does not work well with OAuth via WebAPI
Try replacing [ValidateAntiForgeryToken] with [AutoValidateAntiforgeryToken]
https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs

OIDC Implicit flow - redirect uri length

I'm using OIDC with implicit code flow with response type "id_token token". Everything works great but noticed that callback url with access token, id_token, scope and session_state + domain name already contains 2033 characters. Project that I'm working on needs to support IE 10 and as far as I know there is a limit URL 2048 characters. I'm little afraid that length of callback url is apporaching this limit dangerously.
What is suggested approach with cases like that? Could I change response type to "token" and then request user information from user info endpoint? Or maybe should I do something to reduce size of callback url, try to reduce information in access_token and id_token? Third option seems to be reference token but I'm little afraid about overhead with one extra call to STS.
In project I use oidc-client-js and IdentityServer4.
Thanks
Try to keep the token as small as possible. IOW less claims.
IdentityServer removes all additional claims from the identity token by default in scenarios where an access token is available (unless you override this behavior).
Reference tokens are another way of dealing with that as you said. By enabling caching in the API middleware you can keep the overhead small.
IE is the plague.
Similar issue here but with Electron app. Electron app needs to call protected API. API needs to know identity of calling user. I tried changing response type from "id_token token" to "token" but IdentityServer auth attempt now results in :
UI:
Sorry, there was an error : invalid_scope
Debug Output:
Requests for token response type only must include resource scopes, but no identity scopes
Javascript config (borrowed from Dom's great Javascript sample client):
var config = {
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:5003/callback.html",
response_type: "token",
scope:"openid profile TestApi",
post_logout_redirect_uri : "http://localhost:5003/index.html",
};

Resources