No cache tokens were found during token acquisition - asp.net-mvc

I want to send a request from a Controller of the ASP.NET MVC application that is deployed on the Microsoft Azure Cloud Active Directory and receive a response from the service that is still deployed on the Microsoft Azure Cloud Active Directory.
For this purpose, I downloaded an example you can see from here and customize it for myself. A detailed document of my actions is contained in the same link.
When I tested service and web applications on my azure portal, I encountered an error message in the header:
Failed to acquire token silently as no token was found in the cache.
Call method AcquireToken
Where the error occurred is the following part in my controller:
ClientCredential credential = new ClientCredential( clientId, appKey );
result = await authContext.AcquireTokenSilentAsync( todoListResourceId, credential, new UserIdentifier( userObjectID, UserIdentifierType.UniqueId ) );
clientId: Identifier of my web application installed on Azure AD (For example: c95d45dd-ba7f-41be-a995-1db604afff32)
appKey: Hidden key value of my web application in the portal
todoListResourceId: Identification of my API application installed on Azure AD (For example: 4cfebcb4-6f2e-4eeb-84f2-4220f65774ed)
userObjectID: Value returned from the following piece of code
string userObjectID = ClaimsPrincipal.Current.FindFirst( "http://schemas.microsoft.com/identity/claims/objectidentifier" ).Value;
i.e. a value for the user who is online in the browser. As stated in the document on my GitHub link, this value is not my Microsoft account that I used when logging into my azure portal, but a value for my user that I registered to my Azure Active Directory
A similar topic to this topic has been discussed and answered here before, but this answer has not solved my problem.
I've been working for days, but I haven't gotten a response from the GET, POST, PUT, DELETE methods in the service. I keep dealing with the error in the title. I'm waiting for your help.

The reason you're receiving this error is because the call acquiretokensilentasync is EXPECTED to throw that error when the cache is empty. This call is meant to be caught in a try catch. If it does throw this error it should call the acquiretokenasync call.
In addition to that it looks like you're trying to utilize the client credential flow with the acquiretokensilentasync call, which is not the right method to use per the ADAL wiki docs.
See here on how to do this properly: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Client-credential-flows
It looks like you're using an app id and secret, the method in particular on how to do this per the doc linked above is :
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
AuthenticationResult result = await authenticationContext.AcquireTokenAsync("https://resourceUrl", clientCredential);
More documentation specifically for the acquiretokensilentasync call can be found here : https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-a-cached-token
From the doc above :
Recommended pattern to acquire a token
Now that you have seen both AcquireTokenAsync, AcquireTokenSilentAsync, it's the right moment to
present the recommended usage pattern for calling these methods. The
idea is that you want to minimize the number of signings for the user,
and therefore you'd want to:
first try to acquire a token silently, and if this call fails you try
to get one interactively. Note that, AcquireTokenSilent does not need
to be called in the Client credentials flow (when the application
acquires token without a user, but in its own name)
Note that AcquireTokenSilent can fail for several reasons, such as the
cache does not contain a token for the user, or the token has expired
and cannot be refreshed. For these reasons, a call to
AcquireTokenAsync will usually get a token. But there are also issues
such as network problems, STS unavailability, etc., which won't be
directly solvable. You will see them in more details in the article
about best practices for Handling errors.
In addition to that, it looks like you're using the ADAL Library, I suggest to move over to the MSAL library since Microsoft is slowly moving towards utilizing the MSAL libraries and will at some point in the future (maybe far future) move off of ADAL/V1.0 endpoint. There are no current hard dates for this however. The doc on moving over from ADAL to MSAL can be found here :
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Adal-to-Msal

Related

Should I move the auth code to Access Token exchange to the API for a Web API/SPA OpenID Connect implementation?

I'm trying to get my head around setting up an OpenID Connect server for SSO authentication. I think my basic setup/requirements are pretty standard, but I'm having a little difficulty putting it all together.
The broad setup is a single page application, a web API, and an identity server. The SPA is served from the same domain name as the web API and the ID server is on a different domain, so I might have several SPA/Web API combinations, but of course every case is the same setup (single host with static content and an API). At the moment I'm working with IdentityServer4 to create the identity server; I'm flexible to trying other providers if there's some kind of problem with that one, but so far so good.
My login requirements are also pretty standard I think; I want to have short-lived access tokens and I also want to use refresh tokens to implement a sliding expiration so users don't have to be redirected off of my SPA until they've been inactive for "a while" (however I end up defining that).
After a bit of research, I think what I want is to use the authorization code flow. So generally, the way I thought this would work is:
A user visits the application host (that serves the web API and SPA); the static SPA is served
The SPA loads and determines that there is no access token in local storage. The SPA kicks off the login process by producing a random identifier and storing it in session storage, then navigates the browser to the ID server host
The user authenticates with the ID server host
The ID server hosts redirects to the client and includes in the redirect the random identifier the SPA originally generated along with an authorization code
Upon loading and detecting that it got an access code, the SPA checks session storage for the identifier stored in step 2. Finding it, the SPA calls the web API to exchange the authorization code for an access token
The web API uses a back channel with the ID server to produce an access token and refresh token
The web API stores the refresh token and access token then issues the access token to the client
In all future requests, the client uses the access token with the Web API. When the SPA determines that the access token it has is expired or about to expire, it request a refresh somehow (I'm going to hand-wave the refresh a bit for now)
So I went through the tutorial on the IdentityServer4 site, and to my surprise I ended up in a bit of a different state. It took me a while to work through it; the step I'm talking about if anyone wants to follow along is "Adding a JavaScript Client", but I'd be willing to be the result is common among people implementing OpenID Connect. The resulting flow differed from what I expected starting with step 5; instead of the SPA calling the web API with an authorization code and requesting an access token, the SPA uses CORS and makes a cross-domain request back to the ID server to request the access token. The tutorial didn't really cover refresh tokens all that much (there's other parts of the docs that do, but only briefly), but I think the implication is that if I wanted to use refresh tokens they'd be issued to the client and it would use local storage to store them; then for future refreshes it'd also do a cross-domain request back to the ID server. As a side note, another bit of surprise was that the tutorial has you use PKCE, which on research seems to be unnecessary for a web application; it's somewhat important as including a SHA-2 implementation client-side increases the size of my application by a fair bit.
I believe it is a bad practice to issue a refresh token to a web client and ask it to store it; I'm somewhat vague on the specific vulnerabilities that opens up, but the general idea is that if someone subverts your client somehow, a refresh token is considerably more powerful than a short-lived access token.
So, getting my head around this, I believe the way I originally though this would work was that the web API is the "Relying party" in OAuth 2 parlance, and the tutorial set it up so that the client is the "Relying party". It makes me think that if I want to get a sliding expiration, I have to go past where the tutorial went and move the functionality for token exchange from the client into the web API like I had originally envisioned. It would end up looking a bit like the web API functionally being a proxy for the SPA to exchange the authorization code for an access token.
Ultimately, my question is: am I getting this right? It looks like there are really two different models for implementing OpenID Connect for SPA/API web applications; one where the API is the RP, and another where the SPA is the RP. If you want to use refresh tokens, I think you should go with option 1, but maybe if you care that the API could impersonate the client you'd go with option 2? That still seems like it wouldn't matter to me; that authorization code/access token swap can only be used for a particular application, so it's not like one API could suddenly authenticate as a different backend in that setup. I'm just nervous about going off on my own to structurally alter the setup the tutorial had since this is security-related.
UPDATE
I used the authorization code flow instead of the implicit flow despite the accepted answer, since that's the most recent recommendation of the IETF (see https://datatracker.ietf.org/doc/html/draft-parecki-oauth-browser-based-apps-02#section-4, and a great writeup at https://brockallen.com/2019/01/03/the-state-of-the-implicit-flow-in-oauth2/). I accepted that answer because using a silent refresh via iframe instead of a refresh token seems to be the most standard approach for what I'm trying to do; using that I was able to build a working system that looks like the tutorial. In fact, the client library it recommends (oidc-client) has a built-in function to handle the details. For completeness, what I'm starting off with is this service:
import oidc from "oidc-client";
import Url from "url-parse";
let baseUrl = new Url(window.location.href).set("pathname", "").set("query", "").set("hash", "");
let redirectUrl = (new Url(baseUrl)).set("query", "redirect=fromIdentityProvider");
let silentRedirectUrl = (new Url(baseUrl)).set("pathname", "silent-refresh.html");
let identitySettings = {
authority: "[my application's id server domain]",
client_id: "[my client's id]",
redirect_uri: redirectUrl.toString(),
response_type: "code",
scope: "openid profile [my application's resource name]",
post_logout_redirect_uri: baseUrl,
automaticSilentRenew: true,
silent_redirect_uri: silentRedirectUrl.toString()
};
let userManager = new oidc.UserManager(identitySettings);
let user = null;
export default {
async logIn() {
await userManager.signinRedirect();
},
async isLoggedIn() {
return !!(await this.getAccessToken());
},
async logOut() {
await userManager.signoutRedirect();
},
async getAccessToken() {
user = await userManager.getUser();
return user ? user.access_token : null;
},
async initializeApp() {
let url = new Url(window.location.href, true);
if (url.query && url.query.redirect === "fromIdentityProvider") {
await new oidc.UserManager({
response_mode: "query"
}).signinRedirectCallback();
window.location = "/";
return false;
}
user = await userManager.getUser();
return true;
}
};
Then in my application I call initializeApp when the app starts and getAccessToken before any API calls. I still need to eventually add the ability to automatically redirect on 401 from the API, but that's pretty easy.
To make the silent redirect work, I created silent-redirect.html based on instructions here: https://www.scottbrady91.com/OpenID-Connect/Silent-Refresh-Refreshing-Access-Tokens-when-using-the-Implicit-Flow. I also integrated Google authentication as an external provider and verified that it also works for silent refreshes, so no trade-off there.
To round it out, for me the answer to my original question is basically "no", I don't want to move the exchange step to the backend. I did also decide to use PKCE even though it seems to me like it shouldn't be necessary, it's in the IETF recommendation I mentioned, so I'll stick with that.
There is a special OAuth2 flow for SPAs - the Implicit grant. If you want just an access token, specify &response_type=token when accessing the /auth endpoint. Alternatively, you can ask for an ID token as well with &response_type=token id_token&scope=openid. The SPA gets the token in the redirect URL from the autorization provider (in the hash part #access_token=...) along with its life-time expires_in=.... So the token stays in your browser - the hash part doesn't get sent to the server hosting the SPA files.
Your SPA should validate and keep both values and before the token expiration, it should call the /auth endpoint in an iframe with &prompt=none parameter. If your authorization provider supports Single Sign On (SSO), then you should get a fresh access token without the user noticing it. So it works similarly to a refresh token, without requiring CORS, PKCE or a client secret.
If you wanted to implement some more sophisticated SSO management, take a look at the OpenID Connect Session management RFC.

Always getting "code_already_used" when exchanging code for access token w/Slack API

I'm writing a Slack app that adds Slash commands.
Every time I go through the OAuth flow, when I try to exchange a temporary auth code for an access token, I get the following JSON response:
{"ok"=>false, "error"=>"code_already_used"}
and despite that error message, the two slash commands provided by my app do get installed on the target Slack team.
The desired outcome is: I get a successful response from Slack's API, which contains the access_token and scopes for which the token is valid.
Troubleshooting I've tried so far:
Revoked permissions from my app & uninstalling from target team before trying again
Requesting additional scopes (e.g, commands,channels:history,users.profile:read which I don't need, instead of just commands) to see if that would cause the API to return an access token.
I am able to install on other teams outside of the original team I used when creating the app, but with the same api failure
Any suggestions for how to get the API to return an access token? Thanks in advance!

identity server token for webapi authorization error

I have a number of web api services created that need to get the authenticated user.
I have gotten the id server v3 working such that I can enter /core and /connect/token and my client gets a token and is passing it back to the server.
single iis server running all of the web app and web services and id server.
when I add the token authentication package I am getting an error that the well known configuration can not be found.
I am looking for what I need to change to make this work so that api calls get an authenticated identity.
I think this is a startup problem but I have my app.map() first and then the app.UseIdentityServerBearerTokenAuthentication() after.
so do I make it wait fro the first to complete ? async ? await ?
ok I am slow, I found an option to delay load the metadata and that fixed that.
now the api calls show that the user is authenticated.

Failed to request access token for Fusion Tables API with Google Service Account

I have been successfully using Google API (via HTTP/REST, as well as using the .NET client library) with a Google Service Account to access the files in Google Drive.
Recently, I am exploring the Fusion Tables. I am able to use the API with user authorization via a web application. However, when I try to access it using Google Service Account under the same project, it failed with the below error, whenever I have https://www.googleapis.com/auth/fusiontables in the scope:
https:// www.googleapis.com/oauth2/v3/token
HTTP 401
{"error": "unauthorized_client", "error_description": "Unauthorized client or scope in request." }
The error goes away, when I remove https:// www.googleapis.com/auth/fusiontables and the same code block works fine with https://www.googleapis.com/auth/drive and other scopes.
I have checked and confirmed the "Fusion Tables API" is already enabled for my project at Google Developers Console. (Otherwise, my user authorization via a web application would not be working at the first place.)
Is there anything which I could have missed out? Any help would be greatly appreciated.
I just come across this:
Google drive service account and "Unauthorized client or scope in request"
Even though it does not seems to be related at the first glance, it is indeed the same issue.
Problem resolved after removing User = svcAcct, from the below code block.
ServiceAccountCredential credential;
credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(svcAcct) {
// User = svcAcct, *** removed ***
Scopes = new System.Collections.Generic.List<string>(scopes.Split(' '))
}.FromCertificate(certificate)
);
Hence, here is the general advise:
DO NOT call ServiceAccountCredential.Initializer with User = svcAcct unnecessarily.
Only do this when you are trying to impersonating a difference user
(with the condition that the appropriate setup has been correctly done
in Google Apps Admin Console).
Even though it may not produce any error under certain cases, there
are some unexpected behaviors when including an email address of the
service account itself in the JWT claim set as the value of the "sub"
field.

Renew a JWT issued by Azure ACS

We have Azure ACS configured to issue JWT that is valid for 15 minutes. Once the user is logged-in to the web application (MVC), the user will use the token to access resources on another server (WebAPI). The WebAPI server would then validate that token.
So, is there any way to renew the JWT somehow without interrupting user's work on the web app? We don't want to popup a window and ask the user to sign in again.
Thanks!
If you are using Active Directory Authentication Library (ADAL) for .NET, then it includes a token cache. Per this blog post:
One last thing I’d highlight at this point is that every time you get a token from the authority ADAL adds it to a local cache. Every subsequent call to AcquireToken will examine the cache, and if a suitable token is present it will be returned right away. If a suitable token cannot be found, but there is enough information for obtaining a new one without repeating the entire authentication process (as it is the case with OAuth2 refresh tokens) ADAL will do so automatically. The cache is fully queryable and can be disabled or substituted with your own implementation, but if you don’t need either you don’t even need to know it’s there: AccessToken will use it transparently.
ADAL.NET is available on Nuget here: https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/
If you aren't using ADAL.NET, provide more info, such as:
What library you are using
What is ACS on top of, AD FS or Azure Active Directory
We use ACS + ADAL and there seems to be no clever way to refresh the token. Even if the ExpiresOn Time on the Token inside the Cache is due the AcquireToken always returns the stale cached token. We cache the token ourself, so this code is only invoked when the ExpiresOn is due.
I ended up with this dirty hack:
var authContext = new AuthenticationContext(ServiceInfo.AcsUrl);
if (authContext.TokenCacheStore.Count > 0)
{
authContext.TokenCacheStore.Remove(authContext.TokenCacheStore.First());
}
result = authContext.AcquireToken(acsRealm, allProviders.First());

Resources