Oauth2 userinfo API

Hello,
I would like to suggest adding more “standard” userinfo API for Oauth2 authentication like many other Oauth2 providers has eg: api_url = https://<okta domain>/oauth2/v1/userinfo

I’m setting up a production Grafana instance which should allow authentication against Nextcloud userbase via its Oauth2 interface. So far it was surprisingly easy to set up. Just copy paste the keys and endpoints, but the problem is that Grafana wants email addresses and we use userids.
So far I have coded in grafana.ini:

    [auth.generic_oauth]
    enabled = true
    name = OAuth
    allow_sign_up = true
    client_id = ### Nextcloud key ###
    client_secret = ### Nextcloud key ###
    scopes = user:email,read:org
    auth_url = https://SERVER/index.php/apps/oauth2/authorize
    token_url = https://SERVER/index.php/apps/oauth2/api/v1/token
    api_url = https://SERVER/ocs/v2.php/cloud/user?format=json
    allowed_organizations = OwnORG 

(api_url was just a quick try :))
It’s little bit frustrating how close it went. Grafana redirects nicely to NC and after authentication NC redirects back to Grafana which then issues error as it cannot find email address from the NC’s token. Here is the process how Grafana tries to find the email address:

Grafana will attempt to determine the user’s e-mail address by querying the OAuth provider as described below in the following order until an e-mail address is found:

  1. Check for the presence of an e-mail address via the email field encoded in the OAuth id_token parameter.
  2. Check for the presence of an e-mail address using the JMESPath specified via the email_attribute_path configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the api_url configuration option. Note: Only available in Grafana v6.4+.
  3. Check for the presence of an e-mail address in the attributes map encoded in the OAuth id_token parameter. By default Grafana will perform a lookup into the attributes map using the email:primary key, however, this is configurable and can be adjusted by using the email_attribute_name configuration option.
  4. Query the /emails endpoint of the OAuth provider’s API (configured with api_url) and check for the presence of an e-mail address marked as a primary address.
  5. If no e-mail address is found in steps (1-4), then the e-mail address of the user is set to the empty string.

Your the same problem and when not finding a solution make a change in the oauth application and make a pull request with this functionality.

I managed to get that working on stock Nextcloud 19 with Grafana 7. I used the following in the Grafana OAuth config:

[auth.generic_oauth]
enabled = true
name = Nextcloud
allow_sign_up = true
client_id = <your id>
client_secret = <your secret>
scopes = user:email,read:org
auth_url = https://SERVER/index.php/apps/oauth2/authorize
token_url = https:/SERVER/index.php/apps/oauth2/api/v1/token
api_url = https:/SERVER/ocs/v2.php/cloud/user?format=json&x=
email_attribute_path = ocs.data.email
role_attribute_path = contains(ocs.data.groups[*], 'admin') && 'Admin' || contains(ocs.data.groups[*], 'Grafana') && 'Editor' || 'Viewer'
allowed_organizations =

This assumes members of the groups “admin” or “Grafana” are allowed to edit Grafana dashboards.

1 Like

Nice to see some progress finally with this.
I tried pretty much exactly what you suggested @jum but for some reason it just won’t work. Here is my Grafana config:

[auth.generic_oauth]
enabled = true
name = Nextcloud
allow_sign_up = true
client_id = ***
client_secret = ***
scopes = user:email,read:org
auth_url = https://<SERVER>/index.php/apps/oauth2/authorize
token_url = https://<SERVER>/index.php/apps/oauth2/api/v1/token
api_url = https://<SERVER>/ocs/v2.php/cloud/user?format=json&x=        
email_attribute_name = ocs.data.email
role_attribute_path = contains(ocs.data.groups[*], 'admin') && 'Admin' || contains(ocs.data.groups[*], 'Grafana') && 'Editor' || 'Viewer'

I end getting following error messages in Grafana “Login provider didn’t return an email address”. From Grafana log:

t=2021-02-19T18:44:09+0200 lvl=info msg="Request Completed" logger=context userId=0 orgId=0 uname= method=GET path=/login/generic_oauth status=302 remote_addr="CLIENT IP" time_ms=1 size=372 referer=https://"GRAFANA SERVER":3000/login
t=2021-02-19T18:44:13+0200 lvl=info msg="state check" logger=oauth queryState=*** cookieState=***
t=2021-02-19T18:44:17+0200 lvl=eror msg="login provider didn't return an email address" logger=context userId=0 orgId=0 uname=
t=2021-02-19T18:44:17+0200 lvl=info msg="Request Completed" logger=context userId=0 orgId=0 uname= method=GET path=/login/generic_oauth status=302 remote_addr="CLIENT IP" time_ms=3645 size=29 referer=

Adding “&x=” after api_url seemed to help a little bit forward, but it seems that there is still something not quite right eg. userid=0, uname=
I’m using Nextcloud 20 and Grafana 7. Could you happen to have some ideas what to test next, please?

Does the user you are testing with have an email address in the Nextcloud login data? Grafana requires that but I think it is optional with Nextcloud.

Yes, I have tried put email address in all the fields with my test user, but the result is always the same. I’m trying to get grip of the attribute definitions you have made but still missing something linking these together:

  • What input “x=” waits in api_url?
  • Is email_attribute_path actual pointer to userid json structure (ocs: { data { email…) or similar?
  • role_attribute_path definition seems to have actual code (language?). Will Grafana allow and run code snippets defined in config file?

The x= is a dummy to ignore some extra arguments that Grafana adds to the query but Nextcloud does not understand. The email attr path is indeed the path to the email field in the api response of Nextcloud. Similarly the role_attribute_path is an expression to enable Grafana Admin for members of the Nextcloud groups.

@sushukka Any update on this at all? Did you get it working as I’m having the same issue.

Hi @jum Just wondering if you still have this working? Any special tricks?

Hello,
I never got it working correctly. I managed to get the Nextcloud Oauth button to Grafana login screen, but then the result was what I mentioned before. After searching and reading quite a lot about Nextcloud Oauth service I got a feeling that it won’t age well. It seems that it has been implemented a long time ago and has not got much updates after that. Also I remember seeing something that it won’t be supported in the future or something (not 100% sure anymore of this). As the services I was building back then are now in production and used somewhat heavily, I decided to forget this route. Currently we have separate accounts in Grafana and Nextcloud. One self-built application is using Nextcloud Oauth service, but probably soon migrating to GCP with the authentication part.

It’s a pity, the Nextcloud user management was easy to use and just enough to us. Now need to implement much heavier system to get some small features working eg. this Oauth service authentication.

I have it working fine. For me I had to use lax cookie security to make it work.

Hi @jum , do you happen to have the settings that you adjusted to get this to work? Would really like to get this to work for a number of applications, the built-in OAuth2 is basically useless! :frowning:

I have the following in my generic oauth section of grafana:

#################################### Generic OAuth ##########################
[auth.generic_oauth]
enabled = true
name = Nextcloud
allow_sign_up = true
client_id = szHfcx9iOMZDpODXB7V4zQOMABzB8FHcZSga6qKfuZ4qiFWrItNvYKZ8ZbBHzErz
client_secret = nvNj2m26LmIMx3bZiT3s2jdUMkzWX7f8ZbltnJ7fWzovrq6TX1yBzng8VT0JUZla
scopes = user:email,read:org
auth_url = https://my.domain.org/nextcloud/index.php/apps/oauth2/authorize
token_url = https://my.domain.org/nextcloud/index.php/apps/oauth2/api/v1/token
api_url = https://my.domain.org/nextcloud/ocs/v2.php/cloud/user?format=json&x=
email_attribute_path = ocs.data.email
role_attribute_path = contains(ocs.data.groups[], ‘admin’) && ‘Admin’ || contains(ocs.data.groups[], ‘Grafana’) && ‘Editor’ || ‘Viewer’