Auth0 JWT as access token comes in only on second login - auth0

I have this issue and I'm not sure whether it is a "bug" or my fault somewhere.
All of this is for a SAP on ASP.NET Core Angular which is accessing Auth0 on a hosted page.
I have updated my hosted page Auth0lock object on the hosted page to inculde a params object with a specified audience
var lock = new Auth0Lock(config.clientID, config.auth0Domain, {
auth: {
redirectUrl: config.callbackURL,
responseType: 'token',
params: {
"audience": "https://api.webatom.com"
}
},
assetsUrl: config.assetsUrl,
allowedConnections: connection ? [connection] : null,
rememberLastLogin: !prompt,
language: language,
languageDictionary: languageDictionary,
theme: {
//logo: 'YOUR LOGO HERE',
//primaryColor: 'green'
},
prefill: loginHint ? { email: loginHint, username: loginHint } : null,
closable: false,
// uncomment if you want small buttons for social providers
// socialButtonStyle: 'small'
});
During the first login I get the usual auth result where I receive the JWT as the id_token and a short string for the access token and I don't get a message in auth0 about account access request.
During the second and other logins I get what I want. I get the message and I get the JWT as access token and id_token as null.
How do I get that second result from the start, right from the first login? Is that a bug or am I doing something wrong?
Thank you.
PS: I don't have any rules or hooks implemented at that moment.

As a first step: Add https://jwt.io as an allowed callback to your Client, revert the Auth0 Hosted Login page back to its default (ie. remove the changes you made), then modify the url below with your own settings, and paste it into a browser URL and hit return.
https://{{YOUR_TENANT}}.auth0.com/login?client={{YOUR_CLIENT_ID}}&redirectUrl=https://jwt.io&responseType=token&connection={{YOUR_CONNECTION_NAME}}&audience=https://api.webatom.com&scope=openid
All going well, it should return a JWT Access Token and auto-populate that into the JWT.io text-area.
Next, try this - using Auth0's authorize URL instead. Again, use Auth0 default hosted login page, not the one you modified.
https://{{YOUR_TENANT}}.auth0.com/authorize?client_id={{YOUR_CLIENT_ID}}&protocol=oauth2&redirect_uri=https://jwt.io&response_type=token&scope=openid profile&audience=https://api.webatom.com&nonce=123&state=xyz
Should be same result. And presumably this is what you want every time?
If you do want an Id Token, then simply modify responseType / response_type to be token id_token.
So I would recommend you do not modify the Auth0 Hosted Login page settings for Lock directly (authentication related params..), but instead just send through the parameters you want with the request as per the /authorize endpoint above. If you have a Client application using auth0.js for example, you can set everything up at the Client and send it through when the user authenticates.
Sample snippet for auth0.js library config might be:
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: "https://webapi.com",
responseType: 'token id_token', // just use token if you don't need id token
scope: 'openid profile read:book' // read:book is a scope defined for API
});

So far I have found an interesting work around...
When an opaque token is returned, you can simply copy its aud hash and paste it into the Audience parameter when creating the JwtBearerOptions object into the startup class.
That fixes the error with the invalid audience when using the [Authorize] annotation in the controller api which was the main reason why I needed the jwt from the start.
I thought the only way to get the audience insde the jwt for the JwtBearer to decode it correctly was to set the audience in on the hosted page so it would be returned with the JWT inside the access token.

Related

nextauth with aspnetcore backend

I need a sanity check on what I'm trying to do here.
I want to build a webapp with nextjs where people can log in with discord and as a backend API I want to use a aspnetcore web api.
I got next-auth to work with discord pretty quickly in the frontend. However I'm struggling on how to identify my frontend to my backend.
My plan at the moment is to have my backend create another JWT token and save that somewhere and then use it as the Authorization header in calls to the backend api.
next-auth has callbacks where I can edit the session and the token. So what I plan to do at the moment is just call the backendapi/createJwtToken endpoint, save it to the already existing next-auth token and then into the next-auth session.
Then I could access it anywhere and don't have to refresh until the session is gone.
I can do that with next-auth callbacks
callbacks: {
async session({ session, token, user }) {
session.backendApiToken = token.backendApiToken;
return session;
},
async jwt({ token, account }) {
if (account) { // this fires only on sign in
token.backendApiToken = "ABC - get it from backend/createToken";
}
return token;
},
Is this okay? I know how to create and validate tokens in an aspnetcore api.
Is something unsecure or strange about saving an encoded apiToken in the next-auth token? Or is this absolutely normal?

OpenIddict support returning authorization code via GET request for postman

I have set up an Authorization Server using OpenIddict 3.1.1 (porting over an existing one that was using the older ASOS package directly). I believe I am most of the way there, because when using the client application, I am able to log in, give consent, redirect back to the client, and exchange the authorization code for an access token.
However, when I try to do the same using Postman's OAuth 2.0 authentication support, I am able to log in (and give consent), but when it completes and returns the authorization code, I receive an HTTP 403 from the https://oauth.pstmn.io/v1/callback that I am redirected to:
403 ERROR
The request could not be satisfied.
This distribution is not configured to allow the HTTP request method that was used for this request. The distribution supports only cachable requests. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: UAXpago6ISiqbgm9U_SVPwh96qz1qoveZWFd0Cra-2FximeWZiY2aQ==
From what I can tell, this is because OpenIddict is issuing a POST request back to the callback url. This works for my client application, but evidently is not supported by Postman.
What configuration tweak do I need to make to OpenIddict to support this in postman?
OpenIddict related config in Startup.ConfigureServices:
services.AddOpenIddict()
.AddCore(options => {
options.AddApplicationStore<ClientStore>();
options.UseEntityFramework()
.UseDbContext<OAuthServerDbContext>()
.ReplaceDefaultEntities<Client, Authorization, OAuthScope, Token, long>()
;
})
.AddServer(options => {
options.RegisterClaims();
options.RegisterScopes(OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Profile,
"user");
// flows
options.AllowAuthorizationCodeFlow();
options.AllowRefreshTokenFlow();
options.AllowPasswordFlow();
options.AllowHybridFlow();
// implicit is used by postman
options.AllowImplicitFlow();
var serviceProvider = options.Services.BuildServiceProvider();
var oauthConstants = serviceProvider.GetRequiredService<IOptions<OAuthConstants>>().Value;
var tokenLifetimes = serviceProvider
.GetRequiredService<IOptions<OpenIdConnectServerTokenLifetimeSettings>>().Value;
// security
options.SetAccessTokenLifetime(tokenLifetimes.AccessTokenLifetime)
.SetAuthorizationCodeLifetime(tokenLifetimes.AuthorizationCodeLifetime)
.SetIdentityTokenLifetime(tokenLifetimes.IdentityTokenLifetime)
.SetRefreshTokenLifetime(tokenLifetimes.RefreshTokenLifetime);
options.SetIssuer(new Uri("https://localhost/oauth/"));
// custom handlers added here
options.AddEventHandlers();
// certificate details hidden
options.AddEncryptionCertificate(certificate);
// endpoints
options.SetAuthorizationEndpointUris("/OpenIdConnect/Authorize");
options.SetLogoutEndpointUris("/OpenIdConnect/Logout", "/Account/Logout");
options.SetRevocationEndpointUris("/OpenIdConnect/Revoke");
options.SetTokenEndpointUris("/OpenIdConnect/Token");
options.SetCryptographyEndpointUris("/OpenIdConnect/JWKDoc");
options.SetUserinfoEndpointUris("/OpenIdConnect/UserInfo");
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
//.EnableTokenEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
;
})
.AddValidation(options => {
options.UseLocalServer();
options.UseAspNetCore();
var serviceProvider = options.Services.BuildServiceProvider();
var config = serviceProvider.GetRequiredService<IConfiguration>();
options.SetClientId(config.GetValue<string>(nameof(Settings.OAuthClientId)));
options.SetClientSecret(config.GetValue<string>(nameof(Settings.ClientSecret)));
// certificate details hidden
options.AddEncryptionCertificate(certificate);
});
Postman details:
Authorization
Token Name: Redacted
Grant Type: Authorization Code
Callback URL: disabled, https://oauth.pstmn.io/v1/callback
Authorize using browser: checked
Auth URL: https://localhost/oauth/OpenIdConnect/Authorize
Access Token URL: https://localhost/oauth/OpenIdConnect/Token
Client ID: redacted, but correct
Client Secret: redacted, but correct
Scope: openid offline_access
State:
Client Authentication: Send client credentials in body
edit: The response that it sends to the postman callback URI does include the authorization code in the body, but because of the 403 response, Postman doesn't parse that out and make the follow-up request to exchange the code for the token.
There is an option that you can set to control if the authorization code is received in the URL as a query string or in the body as a post. The option is response_mode and you control that as a client.
I believe if it is not set to response_mode=form_post, then you will get the code in the URL instead.
See the details about this parameter here.

How to implement a login page with JSON Web Token in Ember app with express api

I added authentication for my Express API following this guide and after testing my secret-routes everything seems to work properly. Now my question is how can this be used in an Ember app login page. After receiving the secret token after a successful login how does the browser know you are signed in. How would one log out? How does the ember application know who is signed in? Is there any thing in particular security wise that I should be at tentative to while working on this?
You should use addons to handle most of the heavy lifting for you.
ember-simple-auth-token has setup directions that have you create a login route which will take a username / password and send it to your server for validation. The token response will then be available in your app until the user logs out.
The example looks like
import Controller from '#ember/controller';
import { inject } from '#ember/service';
export default Controller.extend({
session: inject('session'),
actions: {
authenticate: function() {
const credentials = this.getProperties('username', 'password');
const authenticator = 'authenticator:token'; // or 'authenticator:jwt'
this.get('session').authenticate(authenticator, credentials);
}
}
});
You also create the logout route which handles logging out of your app as well as sending any logout request to the server.
If possible you should align your server to the defaults, but you can configure nearly everything.
Authentication Options
ENV['ember-simple-auth-token'] = {
tokenDataPropertyName: 'tokenData'; // Key in session to store token data
refreshAccessTokens: true, // Enables access token refreshing
tokenExpirationInvalidateSession: true, // Enables session invalidation on token expiration
serverTokenRefreshEndpoint: '/api/token-refresh/', // Server endpoint to send refresh request
refreshTokenPropertyName: 'refresh_token', // Key in server response that contains the refresh token
tokenExpireName: 'exp', // Field containing token expiration
refreshLeeway: 0 // Amount of time to send refresh request before token expiration
};
We've been very happy with this addon in production for 3 years and I'd highly recommend it.

Bookmarking login page with nonce

I'm trying to integrate an MVC4 web client with IdentityServer using Microsoft OWIN middleware OIDC authentication v4.0.0. When requesting an ID token from the authorize endpoint, a nonce must be supplied, and the login page served up has the nonce in the query string. If a user bookmarks this and uses it to log in the next day (for example), nonce validation in the client will fail because they'll no longer have that nonce stored, or it will have expired, etc.
This triggers the AuthenticationFailed notification in the client with this exception:
"IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated."
At this point I could HandleResponse, redirect to an error page and so on. If they then try to access a protected resource again, the redirect to IdentityServer immediately returns an ID token due to the previous successful login (from its point of view I guess?) and this time the nonce validates and the user is logged into the client. But this is a rather strange experience for the user - their first attempt to log in appears to fail, they get an error, but then when they try again they don't even have to log in, they're just taken straight in.
An alternative would be to handle this type of exception in AuthenticationFailed by redirecting to the home protected resource so the happens 'seamlessly' in the background. To the user it appears as if their first login attempt worked. But I'm not sure if this is appropriate for genuine nonce validation issues. I'm also worried this may lead to redirect loops in some cases.
So to get to my question... what is the common approach to this issue of bookmarking login pages / nonces? Have I made a fundamental mistake or picked up a fundamental misunderstanding somewhere along the line which has allowed this scenario to occur?
Here is the code that needs to go into the call to
UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = appSettings["ida:Authority"],
ClientId = appSettings["ida:ClientId"],
ClientSecret = appSettings["ida:ClientSecret"],
PostLogoutRedirectUri = appSettings["ida:PostLogoutRedirectUri"],
RedirectUri = appSettings["ida:RedirectUri"],
RequireHttpsMetadata = false,
ResponseType = "code id_token",
Scope = appSettings["ida:Scope"],
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = authFailed =>
{
if (authFailed.Exception.Message.Contains("IDX21323"))
{
authFailed.HandleResponse();
authFailed.OwinContext.Authentication.Challenge();
}
return Task.FromResult(true);
}
},
SignInAsAuthenticationType = "Cookies"
}
});

ember simple auth session, ember data, and passing a Authorization header

I have a working oauth2 authentication process where I get an access token (eg from facebook) using ember simple auth, send it to the back end which calls fb.me() and then uses JWT to create a token. This token is then sent back to the ember app, which then has to send it with every server request, include those requests made by ember-data.
I also need to have this token available after a browser reload.
I have tried many options, where I set a property 'authToken' on the session - I believe that this uses local storage to persist the authenticated session.
But I always seem to have trouble with coordinating the retrieval of this token - either I don't have access to the session, or the token is no longer on the session, or I can't change the ember data headers.
Does anyone have a working simple example of how this can be done - I think it should be easy, but I'm obviously missing something!
Thanks.
Update
The only thing I've been able to get working is to use torii as shown below, but the session content is still lost on refresh - I can see its still authenticated, but its lost the token I set here. So I'm still looking for a real solution.
authenticateWithGooglePlus: function () {
var self = this;
this.get('session').authenticate('simple-auth-authenticator:torii', 'google-oauth2')
.then(function () {
resolveCodeToToken(self.get('session'), self);
});
}
resolveCodeToToken gets the bearer token from the server, sets it on the session and then transitions to the protected page:
function resolveCodeToToken(session, route) {
var authCode = session.content.authorizationCode;
var type = session.content.provider.split('-')[0];
$.ajax({
url: 'http://localhost:4200/api/1/user/auth/' + type,
data: {authCode: authCode}
}).done(function (response) {
// todo handle invalid cases - where user is denied access eg user is disabled
session.set('authToken', response.token);
route.transitionTo('activity', moment().format('DDMMYYYY'));
});
}
And I have a custom authorizer for putting the token (stored in the session) on every request:
import Base from 'simple-auth/authorizers/base';
export default Base.extend({
authorize: function(jqXHR, requestOptions) {
var accessToken = this.get('session.content.authToken');
if (this.get('session.isAuthenticated') && !Ember.isEmpty(accessToken)) {
jqXHR.setRequestHeader('Authorization', accessToken);
}
}
});
I'm not sure why this.get('session.content.authToken') would be undefined after a refresh, I thought by default the session was persisted in local storage. The fact that it is authenticated is persisted, but thats useless without the token since the server will reject calls to protected endpoints.
You'd want to implement your own custom authenticator that first gets a token from Facebook and then sends that to your own server to exchange it for a token for your app. Once you have that you get authorization of ember-data requests as well as session persistence etc. for free.
Have a look at this example: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-multiple-external-providers.html