azure msal-brower with nextJs: passing only one scope to the API - reactjs

I've got a SPA calling an API. The user authenticates using Azure AD and I'm exposing the API with a custom scope (access).
I report below the responses based on different scopes:
scopes = ['access'] => Authorised. Scope in the passed token is: "access"
scopes = ['user.read'] => Not authorised. Scope in the passed token is: "openid profile User.Read email"
scopes = ['access', 'user.read'] => Authorised. Scope in the passed token is: "access"
scopes = ['user.read', 'access'] => Not authorised. Scope in the passed token is: "openid profile User.Read email"
scopes = ['profile', 'email', 'openid', 'access'] => Authorised. Scope in the passed token is: "access"
I don't think it's normal behaviour because I couldn't find any reference around it. What if I need, in the API, the info coming from the User.Read as well?
Dep version: "#azure/msal-browser": "^2.26.0"

I tried to reproduce the same in my environment and got the same results.
When I passed ['access', 'user.read'] ,I got token for only access scope like below:
Please check the aud (audience) claim of the token you are generating.
If you are giving access as your scope, your aud will be api://your_app_id.
If you are giving user.read as your scope, your aud will be 00000003-0000-0000-c000-000000000000 that means graph.microsoft.com.
If you are giving 2 different scopes like ['access', 'user.read'], it will only consider first scope and generate token for that specific audience like below:
As mentioned by Juunas in this SO Thread, access token is valid for one API only based on the audience.
If you want the info coming from the User.Read, you need to generate two tokens, one for your API scope (access) and another for Graph API scope (user.read).
For more in detail, please refer below link:
azure - Passing multiple scope values to Oauth token endpoint - by Hari Krishna

Related

What Scopes Required For UserInfo Endpoint - Besides 'openid'?

I'm using IdentityServer 4 with .NET Core.
I can successfully log in using grant type ResourceOwnerPassword ('password'), then I immediately call the UserInfo endpoint. That call fails, and when I check the server log I see:
'Scope validation failed. Return 403'
and
'Scopes found on current principal: scope: openid, scope: profile, scope: roles, scope: api1, scope: offline_access'
The docs say 'at least the openid scope is required', which the log shows I have, but the docs also say, right before that, '...Depending on the granted scopes...' - what scopes? Where is it designated what scopes are required by the UserInfo endpoint? I'm assuming I'm missing it/them, but no idea what. I have correct scopes to successfully hit the token endpoint (/connect/token), but not the right scope(s) to successfully call the userinfo endpoint (/connect/userinfo).
Can anyone help?
Thanks!
Buzz
The userinfo endpoint has a minimum requirement for the openid scope.
Here's the test that proves that:
https://github.com/IdentityServer/IdentityServer4/blob/dev/test/IdentityServer.IntegrationTests/Clients/UserInfoClient.cs#L41
Ok I figured this out - I was missing an AllowedScope on the call to the UserInfo endpoint. When I added the scope here in the Startup.Configure() method it worked:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000/",
AllowedScopes = { "api1", "WebAPI" },
RequireHttpsMetadata = false
});
The thing I don't understand is how the login itself was working. The scopes are sent in on the call to login (/connect/token) - and that succeeded. I thought these scopes were for authentication, as the name for this class here is '..AuthenticationOptions'. My guess is these are options during login but not for login, and they're only used when you go out to access a resource, such as /connect/userinfo. Maybe someone who knows can confirm this.
Thanks for your help!
Buzz

How do I return the id property of the user object in Apollo?

When I place the below in a React component that queries the user model I am able to get the entire user object as queried by graphQL including the id property:
console.log(this.props.data.user)
But when I try to access the id property from code:
console.log(this.props.data.user) // undefined
console.log(this.props.data.user.id)
// error cannot get property of undefined
My first guess is that this is a security feature; I am using Auth0 and Graphcool.
But I may just be going about this in a backwards way. If so, any help on accessing the user id in the correct manner would be appreciated.
Thanks
This is covered in the FAQ article on the logged in user.
Obtaining a Signed JWT with Auth0
The user query returns the currently authenticated user. So first we have to think about how the authenticated user is determined.
The current state-of-the-art is using verified JWT and passing them as the Authorization header.
After entering valid credentials in Auth0 Lock, it returns a JWT that is signed with your secret Auth0 key. This signed JWT is sent to the GraphQL server where we'll use your Auth0 key to verify it and if it belongs to a valid user, the request is authenticated.
Setting the Authorization Header with Apollo Client
So I suspect that you're simply not passing a valid Authorization header. With Apollo, you can use this to ensure passing the token if it is present. Note that we'll use local storage for storing the token from Auth0 Lock:
const networkInterface = createNetworkInterface({ uri: 'https://api.graph.cool/simple/v1/__PROJECT_ID__' })
// use the auth0IdToken in localStorage for authorized requests
networkInterface.use([{
applyMiddleware (req, next) {
if (!req.options.headers) {
req.options.headers = {}
}
// get the authentication token from local storage if it exists
if (localStorage.getItem('auth0IdToken')) {
req.options.headers.authorization = `Bearer ${localStorage.getItem('auth0IdToken')}`
}
next()
},
}])
const client = new ApolloClient({ networkInterface })
Check this Auth0 example code or the live demo to see how it works.
You might also be interested in this answer on authorization (permissions).

How to access Devise_token_auth access token of omniauth provider?

How do I access the access token of the omniauth provider that should be sent back after successful authentication with the devise_token_auth gem and ng-token-auth with angular? I'd like to store this token to make future requests to the omniauth provider for updating information. The omniauth provider specifically is Strava. I see that devise_token_auth is creating its own access tokens, but those aren't for accessing Strava. Also I have no idea even where devise token auth pulls in the information from Strava even after reading through the gem's code. Seems like this should be a pretty simple thing, I can't be the only one who wants this information returned. Thanks in advance.
We have been able to figure this out with a lot of experimentation. #resouce does return nil for us too, but we did find the access-token and all the other information returned from the omniauth provider, located in request.env['omniauth.auth'], located in the redirect_callbacks action of the OmniAuthCallbacks controller. We also needed to set up
mount_devise_token_auth_for 'User', at: 'auth', controllers: { omniauth_callbacks: 'registrations'}
in routes.rb, and create the custom controller which we named RegistrationsController.
So our block looked like this
class RegistrationsController < DeviseTokenAuth::OmniauthCallbacksController
def redirect_callbacks
super
puts "REDIRECT:"
puts request.env['omniauth.params']
puts "AUTH INFO"
puts request.env['omniauth.auth'].credentials['token']
puts "REDIRECT END"
# create the user_strava_key and save it.
#strava_token = request.env['omniauth.auth'].credentials['token']
UserStravaKey.create(key_secret: #strava_token)
end
end

What are scope values for an OAuth2 server?

I'm facing a difficulty to understand how scopes work.
I found here a small text that describes the scopes of stackexchange api but i need more information on how they work (not specifically this one...). Can someone provide me a concept?
Thanks in advance
To authorize an app you need to call a URL for the OAuth2 authorization process. This URL is "living" in the API's provider documentation. For example Google has this url:
https://accounts.google.com/o/auth2/auth
Also you will need to specify a few query parameters with this link:
cliend_id
redirect_uri
scope: The data your application is requesting access to. This is typically specified as a list of space-delimited string, though Facebook uses comma-delimited strings. Valid values for the scope should be included in the API provider documentation. For Gougle Tasks, the scope is https://www.googleapis.com/auth/tasks. If an application also needed access to Google Docs, it would specify a scope value of https://www.googleapis.com/auth/tasks https://docs.google.com/feeds
response_type: code for the server-side web application flow, indivating that an authorization code will be returned to the application after the user approves the authorization request.
state: A unique value used by your application in order to prevent cross-site request forgery (CSRF) attacks on your implementation. The value should be a random unique string for this particular request, unguessable and kept secret in the client (perhaps in a server-side session)
// Generate random value for use as the 'state'. Mitigates
// risk of CSRF attacks when this value is verified against the
// value returned from the OAuth provider with the authorization
// code.
$_SESSION['state'] = rand(0,999999999);
$authorizationUrlBase = 'https://accounts.google.com/o/oauth2/auth';
$redirectUriPath = '/oauth2callback.php';
// For example only. A valid value for client_id needs to be obtained
// for your environment from the Google APIs Console at
// http://code.google.com/apis/console.
$queryParams = array(
'client_id' => '240195362.apps.googleusercontent.com',
'redirect_uri' => (isset($_SERVER['HTTPS'])?'https://':'http://') .
$_SERVER['HTTP_HOST'] . $redirectUriPath,
'scope' => 'https://www.googleapis.com/auth/tasks',
'response_type' => 'code',
'state' => $_SESSION['state'],
'approval_prompt' => 'force', // always request user consent
'access_type' => 'offline' // obtain a refresh token
);
$goToUrl = $authorizationUrlBase . '?' . http_build_query($queryParams);
// Output a webpage directing users to the $goToUrl after
// they click a "Let's Go" button
include 'access_request_template.php';
The set of query string parameters supported by the Google Authorization Server for web server applications are here:
https://developers.google.com/accounts/docs/OAuth2WebServer?hl=el#formingtheurl

Google Channel API sending message with token

In documents it says 'client_id' part can actually be the token, however it doesn't work. Anyone know why?
https://developers.google.com/appengine/docs/python/channel/functions
If the client_id parameter is actually a token returned by a create_channel call then send_message can be used for different versions of the app. For instance you could create the channel on the front end and then send messages from a backend of the app.
the reason i want to use this, is because i want to send messages to anonymous users as well, without requiring them to login. i don't know if it is possible to assign them a 'client_id' if token doesn't work.
this is how i am creating the token
user = users.get_current_user()
if user:
token = channel.create_channel(user.user_id())
else:
token = channel.create_channel(str(uuid.uuid4()))
then injecting into client
template_values = {
'token' : token,
}
on the client side open the channel
openChannel = function() {
var token = '{{ token }}';
var channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpened,
'onmessage': onMessage,
'onerror': function() {},
'onclose': function() {}
};
var socket = channel.open(handler);
socket.onopen = onOpened;
socket.onmessage = onMessage;
}
now send a message
var xhr = new XMLHttpRequest();
xhr.open('POST', path, true);
xhr.send();
in the server,
when the message is received send back a message using the token
channel.send_message(token, someMessage)
back to client
onMessage = function(m) {
alert("you have some message");
}
this sequence works fine if client_id() is used instead of token when calling send_message
In response to btevfik's initial question: Allowing tokens or client_id in send_message is a feature released in 1.7.5 (very recently). Some people may not be familiar with it yet so therefore they suggest to use client_id. Both should work!
The only thing that I can see in your code is the fact that you should not rely on token variable to be correct in between two requests. They may not even land on the same instance of the app. If you share your code with more details I may be able to spot something. The proper way would be to either store the token in the datastore or pass it from the client as a parameter when you send the message that will trigger a message back.
The purpose of this feature was to allow people to send messages from backends (or other versions). Before was not possible whereas now you can do it if you use directly the tokens instead of the client_id.
Long time this post has been around, but just curious about your usage of the token global variable?
I don't see this code:
global token
before you set the token
user = users.get_current_user()
if user:
token = channel.create_channel(user.user_id())
else:
token = channel.create_channel(str(uuid.uuid4()))
If that code is missing, then token will be set in the local scope of the function above and not globally. So, the token value used later will be None (or to what ever the token was initialised with.)
Just a thought, if its still relevant.
I don't think you actually have a problem here.
You are able to send messages to users that are logged in or not.
The problem you are having I think is knowing that there are multiple ways to use the channel API re: tokens.
https://developers.google.com/appengine/docs/python/channel/overview#Life_of_a_Typical_Channel_Message
In this example, it shows the JavaScript client explicitly requests a token and sends its Client ID to the server. In contrast, you could choose to design your application to inject the token into the client before the page loads in the browser, or some other implementation if preferred.
This diagram shows the creation of a channel on the server. In this
example, it shows the JavaScript client explicitly requests a token
and sends its Client ID to the server. In contrast, you could choose
to design your application to inject the token into the client before
the page loads in the browser, or some other implementation if
preferred.
Here's my demo implementation, hope it helps somehow: https://github.com/Paul1234321/channelapidemo.git
Here's the code for creating the channel on GAE:
client_id = str(uuid.uuid4()).replace("-",'')
channel_token = channel.create_channel(client_id)
And in the JS:
channel = new goog.appengine.Channel('{{ token }}');
Have a look at it in action: http://pppredictor.appspot.com/
You shouldn't store request-specific values in global variables. Store them in a cookie or pass them as a request parameter instead.

Resources