Express API with JWT returns "Unauthorized: Invalid Algorithm error" - reactjs

I've written a simple React app, following the instructions of a slightly out of date tutorial that is meant to display a list of contacts in the sidebar with individual contact data displayed in an index component but only if you have been authenticated by logging into an Auth0 component and have a JSON Web Token kept in local storage. I have confirmed that I am logged in and have the token. Everything up to this point is working fine.
The problem begins when I click on a contact in the sidebar to view that contact's data, which comes down from a pretty basic API set up with Express. I've been using Postman to troubleshoot since the only error I get from app is "401: Unauthorized"
When I hit my endpoint, suppling an Authorization header with "Bearer [JWT_VALUE_HERE]" Postman responds with "UnauthorizedError: invalid algorithm"
The full output
UnauthorizedError: invalid algorithm
at /Users/Arkham/Documents/Web/eldrac/react-auth-server/node_modules/express-jwt/lib/index.js:102:22
at /Users/Arkham/Documents/Web/eldrac/react-auth-server/node_modules/jsonwebtoken/verify.js:27:18
at _combinedTickCallback (internal/process/next_tick.js:95:7)
at process._tickCallback (internal/process/next_tick.js:161:9)
I've done a bit of googling and tweaked my Auth0 client settings, specifically the Algorithm settings but no matter what I set it to (my options are RS256 and HS256), it doesn't seem to make a difference. I'm dying to get past this.
I use Superagent to make my request
getContact: (url) => {
return new Promise((resolve, reject) => {
request
.get(url)
.set('Authorization', 'Bearer ' + AuthStore.getJwt())
.end((err, response) => {
if (err) reject(err);
resolve(JSON.parse(response.text));
})
});
}
Which seems to be working. I have confirmed that the url is correct and that AuthStore.getJwt() are supplying the correct parameters.

Your question does not provide much information necessary to diagnose the issue - First of all, you should be sending a JWT Access token to the API, not your Id Token.
Upfront questions:
Do you have an API defined in the Auth0 Dashboard?
When you authenticate, are you using an audience parameter? It is
likely that the Access Token is using RS256. Remember for an access
token and Resource API, it is the API that controls the algorithm,
not the Client.
What algorithm is your API using to verify the token?
Presumably, the url in your code snippet is something like
http://localhost:3001/myendpoint?
Take your token, and paste it at https://jwt.io to see what the algorithm used is. Compare that with what is being used to verify the token.
Shall update my answer here as you give more information - please use comments section for this answer.

Related

How do you handle authentication and token refresh with Microsoft Identity and Azure AD

I'm attempting to secure a .Net 6.0 / Razor Page web application against Azure AD. I was able to complete the application registration with Azure AD and successfully authenticate users. The issue I'm facing occurs when the issued token expires. I have some experience working with Angular and IdentityServer implementations, but Razor Page/Microsoft Identity is still new to me.
What I would like to happen:
The user logs in with their Microsoft account
The user's session is uninterrupted for up to 12 hours (with all token management happening behind the scenes)
After 12 hours the session/cookies will expire and the user will need to log in again
What is happening:
The user logs in and is authenticated
After approximately one hour, the application triggers a call to the /authorize endpoint the next time the user takes any action (such as trying to navigate to a new page)
This causes the application to reload on the page the user was currently on (thus interrupting their experience)
Additional Issue: I am also receiving a CORS error under similar circumstances as above. The difference here is this is occurring when the user is in the middle of form data entry when the (presumed) token expiration occurs. When they click submit to post the form, a 302 xhr / Redirect to the /authorize endpoint is triggered. This call results in a CORS error. Refreshing the page is required to trigger a successful call (and they need to start over on their form). Update: This is occurring due to an AJAX call (nothing to do with the form/post specifically). See the edit at the end.
Ideally, I would like the token to be automatically (and silently) refreshed via a refresh token once it is nearing expiration. I would also, of course, like to avoid the scenario of the CORS error when they are attempting to post when the token has expired.
Some code snippets (note: I'm manually adding authentication to an existing app, I did not use any scaffolding/templates for the initial project creation).
Note: I initially tried the below implementation without defining custom authOptions, but during debugging and different attempts at resolution, it exists in the below state. Results were consistent either way.
Program.cs
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
var services = builder.Services;
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
authOptions =>
{
config.Bind("AzureAD", authOptions);
authOptions.MaxAge = TimeSpan.FromHours(12);
authOptions.SaveTokens = true;
},
sessionOptions =>
{
sessionOptions.Cookie.MaxAge = TimeSpan.FromHours(12);
sessionOptions.Cookie.Name = "Custom-Cookie-Name";
sessionOptions.ExpireTimeSpan = TimeSpan.FromHours(12);
sessionOptions.SlidingExpiration = false;
})
.EnableTokenAcquisitionToCallDownstreamApi(config.GetValue<string>("GraphApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(config.GetSection("GraphApi"))
.AddSessionTokenCaches();
services.AddRazorPages(options =>
{
options.Conventions.AddPageRoute("/Disclaimer", "/");
})
.AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddHttpContextAccessor();
........
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.UseSaveUserDetailsOnAuthentication();
app.UseIdentityPageInitialization();
app.MapRazorPages();
app.MapControllers();
app.Run();
I also have some middleware that is using the graph service to hit the /me endpoint and store some user details under specific conditions (in case this is relevant):
Graph Middleware
public async Task InvokeAsync(HttpContext context, UserManager<ApplicationUser> userManager, GraphServiceClient graphServiceClient)
{
var page = context.GetRouteValue("page")?.ToString();
if (!page.IsNullOrEmpty() && page.Equals("/Disclaimer") && context.User.Identity?.IsAuthenticated == true)
{
var user = await graphServiceClient.Me
.Request()
.GetAsync()
.ConfigureAwait(false);
The below snippet is what occurs when attempting the post scenario above.
The tl/dr questions are, using the Microsoft Identity libray/MSAL, how do I:
Silently refresh a user's token
Avoid reloading the page to get a new token (i.e.: calling /authorize and redirecting to obtain a new token)
Handle token expiration from the client-side (avoid the CORS error when posting a form). Do I need to add an additionally client-side js library to manage this?
I've tried scouring Microsoft's documentation, but nothing I've found goes into detail on this. The closest I found was MSAL's documentation mentioning that it handles token refresh for you (but it seemingly isn't happening in my case).
I'm expecting that the token will be silently refreshed by the underlying MSAL library, but that does not appear to be happening. Additionally, I'm expecting to avoid CORS errors on the front-end related to token expiration.
EDIT: While my main question still remains, I believe I found the resolution for the secondary issue: the CORS issue which is actually triggered via an AJAX call to the API. This article outlines that Microsoft.Identity.Web v1.2.0+ now handles this scenario. I now have a vague idea on how to handle it, but still need to attempt the implementation.
I found a reference here explaining that these session token caches have a scoped lifetime and should not be used when TokenAcquisition is used as a singleton, which I believe is the case with the use of the Microsoft Graph API ("AddMicrosoftGraph").
I switched the session token cache to a distributed SQL token cache. However, I do not believe any of this was actually the root issue.
I've identified an issue causing my server (clustered behind a LB without sticky sessions) encryption keys to not be correctly stored/shared in a distributed store. What was happening is any idle timeout in ISS would reset them, causing the auth cookie to be unusable. Additionally, any time the app would hit a different web server behind the LB, the existing auth cookie to be unusable by the new server (because they were using separate keys). So in both scenarios the application would redirect the user for authentication.
The fix for this was simply implementing a distributed key store as described here. The provided stores did not work for me, due to restrictions put in place by my client, so I just implemented a custom IXmlRepository and registered it:
services.Configure<KeyManagementOptions>(options => options.XmlRepository = new CustomXmlRepository());
So at the end of the day I had the following issues:
The auth cookie was becoming invalidated due to changing/lost keys as described above: Resolved by adding a distributed key store
The Microsoft GraphServiceClient was unable to obtain access tokens/refresh tokens (resulting in MSAL errors), due to a lack of a distributed token store as well as due to changing/lost keys (when I was storing tokens in the cookies): Resolved by adding a distributed token store (described here)

Django and react login with google authentication

I was trying set up google authentication with react frontend and django rest framework backend. I set up both the frontend and backend using this two part tutorial, PART1 & PART2. When I try to login with google in the frontend I get POST http://127.0.0.1:8000/google-login/ 400 (Bad Request) I think it's because my google api needs an access token and an authorization code to be passed. After debugging the react js, I noticed the response I get from google doesn't have an authorization code. I suspect because responseType is permission(by default), Source:React login props , instead of code. I was wondering how would you change the response type in react? (I'm not even sure if this alone is the issue)
Here's my backend code
In my views.py file
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = "http://localhost:3000"
client_class = OAuth2Client
in my urls.py
path('google-login/', GoogleLogin.as_view(), name='google-login'),
for my front end
/Components/login.js
const googleLogin = async (accesstoken,code) => {
console.log(accesstoken)
let res = await cacaDB.post(
`google-login/`,
{
access_token: accesstoken,
code: code
}
);
console.log(res);
return await res.status;
};
const responseGoogle = (response) => {
console.log(response.code);
googleLogin(response.accessToken, response.code);
}
return(
<div className="App">
<h1>LOGIN WITH GOOGLE</h1>
<GoogleLogin
clientId="client_id"
buttonText="LOGIN WITH GOOGLE"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
</div>
)
I want to save the user in the database and have them stay logged in, in the front end.
This Post explains the login flow behind the scene. Here's Login flow image I'm basically stuck on returning code and accesstoken(I can return this successfully) step.
Here's my list of questions,
How do I return code from google?
I have knox token set up, can I
use it instead of the JWT tokens?
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
Would really appreciate your inputs.
After investigating a bit on my end, I think I might have a solution that works for you.
I've messed with OAuth before, and it's quite tricky sometimes because it has to be robust. So a bunch of security policies usually get in the way.
I'll provide my full step-by-step, since I was able to get it working, trying my best to match what you posted.
Firstly, to have a clean slate, I went off the example code linked in the tutorials. I cloned and built the project, and did the following:
Creating a new project on GCP
Configured the OAuth consent screen
I set the User type to "internal". This options may not be available if you're not using an account under GSuite (which I am). "External" should be fine though, just that "internal" is the easiest to test.
Created a OAuth 2.0 Client
Added http://localhost:3000 to the "Authorized JavaScript origins" and "Authorized redirect URIs" sections
Register a Django superuser
Registered a Site, with value of localhost:8000 for both fields.
Went into the admin panel, and added a Social Application with Client ID and Secret Key as the "Client ID" and "Client Secret" from GCP, respectively. I also picked the localhost site that we added earlier and added it to the right hand box. (I left Key blank)
Example of my Application Page
Filled in the clientId field in App.js, in the params of the GoogleLogin component.
Here's where I ran into a bit of trouble, but this is good news as I was able to reproduce your error! Looking at the request in the network inspector, I see that for me, no body was passed, which is clearly the direct cause of the error. But looking at App#responseGoogle(response), it clearly should pass a token of some sort, because we see the line googleLogin(response.accessToken).
So what is happening is that accounts.google.com is NOT returning a proper response, so something is happening on their end, and we get an invalid response, but we fail silently because javascript is javascript.
After examining the response that Google gave back, I found this related SO post that allowed me to fix the issue, and interestingly, the solution to it was quite simple: Clear your cache. I'll be honest, I'm not exactly sure why this works, but I suspect it has something to do with the fact that development is on your local machine (localhost/127.0.0.1 difference, perhaps?).
You can also try to access your site via incognito mode, or another browser, which also worked for me.
I have knox token set up, can I use it instead of the JWT tokens?
I don't think I have enough knowledge to properly answer this, but my preliminary research suggests no. AFAIK, you should just store the token that Google gives you, as the token itself is what you'll use to authenticate. It seems that Knox replaces Django's TokenAuthentication, which means that Knox is in charge of generating the token. If you're offloading the login work to Google, I don't see how you could leverage something like Knox. However, I could be very wrong.
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
I believe so. After successfully authenticating with Google (and it calls the backend endpoint correctly), it seems to create a "Social Account" model. An example of what it created for me is below. It retrieved all this information (like my name) from Google.
Example of my "Social Accounts" page
As for how to retrieve the login from the browser's local storage, I have no idea. I see no evidence of a cookie, so it must be storing it somewhere else, or you might have to set that up yourself (with React Providers, Services, or even Redux.

Google auth2 .getCurrentUser() get the user token?

I'm using react in the front end and nodeJS in the backend.
I'm trying to have a google auth2 login flow from the front end.
Where can I get the token from a google auth2 login?
I see it in the response under Zi.access_token but it doesn't seem to me to be the right way to get it.
In order to access properties such as basic_profile, access_token, the Auth2 library provides a range of getter-functions that will allow you access the data in a predictable way.
For access_token, you should call the function getAuthResponse() on the GoogleUser object returned from a successful login, this GoogleUser object will contain a key clearly labeled access_token
For basic_profile, GoogleUser.getBasicProfile() is provided
I see it in the response under Zi.access_token but it doesn't seem to me to be the right way to get it.
You are right, and this is something perhaps the docs can mention.

JIRA Cloud REST API (OAuth 2.0) Error 403 on POST Requests

I am trying to connect my React app to the Jira Cloud API and can't seem to get past a 403 error.
My code currently does a Auth dance using OAuth 2.0 and returns the token and cloudid. I can use this to GET issues, however POST request (like creating an issue) return with 403. I have found here that this error is returned if the user does not have the necessary permission to access the resource or run the method.
I have ensured the user has the correct scope ([write: jira-work, read: jira-work]) and verified this is reflected in the user account (in their account > connect apps tab).
My app is not linked (via ApplicationLink) or installed (via Apps, Manage Apps), is this necessary to perform POST requests?
Here is a sample of my code:
fetch(`https://api.atlassian.com/ex/jira/${jira.cloudid}/rest/api/2/issue/`, {
method: "POST",
headers: {
"Content-Type": 'application/json',
"Authorization": `Bearer ${jira.token}`
},
body: JSON.stringify(data)
})
.then(...)
Neither api version 2 or 3 are working for this POST request. I have explored using Basic Auth however this fails due to CORS errors.
I have verified that the POST request does work in POSTMAN (using the cloudid and token).
---------------------------------------------------------------------------------------------------------------------------
UPDATE
After talking to Atlassian Staff, there is an issue within their API security:
"By trying the same thing you mentioned I think I found what the problem is. Your request likely fails with a ‘XSRF check failed’ in the browser.
I’ve already talked to one of our security engineers and we quickly dived into the implementation code to confirm why this not working and what would need to be changed on our side. We’ve also already opened a engineering ticket to get this addressed. This will likely take a few weeks to get addressed, but I’ll keep you posted if I hear any updates!"
The XSRF check failed was the main error for my 403 response. I'll post any updates I receive and answer the question when a resolution is found.
This has apparently been resolved. Follow the discussion here: https://community.developer.atlassian.com/t/jira-cloud-rest-api-oauth-2-0-error-403-on-post-requests/25621/4

Alexa, Unable to link your skill

I am creating custom skill in Alexa which uses account linking. I have created my own authentication server using OAuth2 php library and I have configured the authorization url and token urls in skill configuration.
When I try account linking from Alexa mobile app, I get error 'unable to link your skill'. following is my work progress.
Alexa app is able to open my authentication url.
I am able authorize and provide authorization code with redirect uri.
Alexa is requesting for access token using authorization code previously provided.
I am able validate authorization code and response back with access token and refresh token.
Alexa fails in this step to link with my skill. It say's 'Unable to link your skill'.
I have gone through my forums about the same, but couldn't find what exactly the issue is. Could any one please help me out in this regard.
I was facing the same issue too , the problem was solved by selecting "credentials in request body" (default being Http basic) for "Client Authentication Scheme", since the access token in my case was sent in the body of the message. Check how the authentication token is sent by your server.
If your redirect link is currently:
https://layla.amazon.com/api/skill/link/xxxxxxxxxxxxxx?code=xxxxxxxxx&state=xxxxx
You need to change the ? to a #
e.g.
https://layla.amazon.com/api/skill/link/xxxxxxxxxxxxxx#code=xxxxxxxxx&state=xxxxx
Thought this might help anyone wondering how the Alexa service is posting to their OAuth endpoint since it's pretty opaque and undocumented. The redirect to the Alexa service initiates a POST request to the defined OAuth endpoint with the post body in x-www-form-urlencoded format not JSON. So the POST looks like this.
​
POST /authentication/1/oauth HTTP/1.1 url.Values{} grant_type=authorization_code&code=XXXXXXXXXXXXXXXXXXXXXXXXX&redirect_uri=https%253A%252F%252Fpitangui.amazon.com%252Fapi%252Fskill%252Flink%252FM9BEOG3DM65SQ&client_id=XXXXXXXXXXXXXXXXXXXXXX
If your endpoint isn't parsing this data or expecting some format that can be unmarshaled easily then it is probably failing with a 406 response.
In my case the problem was with the Client secret,
In google developer console add your skill redirect URIs
and recheck the client secret you provide in the alexa skill Authorization grant type
My issue was with the final AccessToken call. I was assuming it was using a GET request, so I only catered for this in my function. It is actually creating an access token. So it's using a POST.
After I updated my function to use a post and return the AccessToken in JSON format it all works fine.
Maybe the following steps will help you identify the problem:
Add console.log('LOGS', response) to your Lambda function.
Activate the skill and login in the Alexa app
Go back to your Lambda function and check the last logs for the LOGS entry.
If you find that the Lambda function is invoked than the problem is not from your OAuth server, but you may need to handle the "AcceptGrant directive" in your Lambda function as it is motioned here: https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-authorization.html#directives
adjust your Lambda function to:
exports.handler = function (request, context) {
if (request.directive.header.namespace === 'Alexa.Authorization' && request.directive.header.name === 'AcceptGrant') {
log("DEBUG:", "Authorization request", JSON.stringify(request));
handleAcceptGrant(request, context);
}
function handleAcceptGrant(request, context) {
var response = {
event: {
header: {
"namespace": "Alexa.Authorization",
"name": "AcceptGrant.Response",
"messageId": request.directive.header.messageId,
"payloadVersion": "3"
},
payload: {}
}
};
log("DEBUG", "Alexa.Authorization ", JSON.stringify(response));
context.succeed(response);
}
If the problem is with the AcceptGrant then The account linking should be now successful.

Resources