Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Research Feide API Gatekeeper #2484

Closed
Tracked by #2481
hmpf opened this issue Oct 26, 2022 · 2 comments
Closed
Tracked by #2481

Research Feide API Gatekeeper #2484

hmpf opened this issue Oct 26, 2022 · 2 comments
Assignees

Comments

@hmpf
Copy link
Contributor

hmpf commented Oct 26, 2022

A NAV admin must also be able to configure NAV to trust JWTs that are signed by a third party - like adding a trusted public key to a config option in webfront.conf

As we have funds to connect with the Feide API Gatekeeper that's an obvious place to start.

@stveit
Copy link
Contributor

stveit commented Oct 26, 2022

In the Feide docs for jwt token stuff there is a scary line:

This is a proof of concept implementation. It may be changed or discontinued at short notice.

@stveit
Copy link
Contributor

stveit commented Feb 22, 2023

JWT with Feide

Compilation of data from feide docs and some stuff i figured out myself

https://docs.feide.no

Generating JWT tokens

Client tokens

The easiest way to get a JWT token is to authenticate as a client. A client in this case refers to an Application registered in dataporten. This requires you to know the CLIENT_ID and CLIENT_SECRET for the client. This information is found in Dataporten for the relevant application.

First, in order to get an access_token (not JWT yet) you must send a request to a certain Feide endpoint:

export CLIENT_ID=4cc40647-0d56-4de2-9f8f-975ff1ca55a8
export CLIENT_SECRET=b54cbb37-1a75-41bc-8471-c44045d6b0e2
curl -X POST https://auth.dataporten.no/oauth/token -u $CLIENT_ID:$CLIENT_SECRET -d 'grant_type=client_credentials'

Expected output:

"access_token": "9c256322-7c11-4220-93d6-fc5ffad3cb13",
"token_type": "Bearer",
"expires_in": 28799,
"scope": "foo bar"

I think the scope value in the output lists all scopes you can request access to, but not sure.

This access_token can then be exchanged for a JWT token by sending it to another endpoint. But first you must find the correct value for the audience field. This is based on the API registered in the API Gatekeeper. The audience name is based on the Identifier for the API (you can find this value in the in Dataporten if you find API gatekeepers and click on the relevant API). For an API with Identifier=coolapi, the audience will be https://n.feide.no/datasources/coolapi.

Another thing you need to know is what scopes you want to request. These are added to the request with the scope key, and the value should be a space separated list of scopes. If you want the scopes foo and bar, then you add &scope=foo bar.

After configuring all these things the request should look like this:

export CLIENT_ID=4cc40647-0d56-4de2-9f8f-975ff1ca55a8
export CLIENT_SECRET=b54cbb37-1a75-41bc-8471-c44045d6b0e2
export ACCESS_TOKEN=9c256322-7c11-4220-93d6-fc5ffad3cb13
export AUDIENCE=https://n.feide.no/datasources/coolapi
export SCOPE="foo bar"

curl -x POST https://auth.dataporten.no/oauth/token -d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&audience=AUDIENCE&subject_token=96c5a3aa-1af8-45bd-a4f8-4b7fb07d393f&subject_token_type=urn:ietf:params:oauth:token-type:access_token&scope=SCOPE'

The response should then look something like this:

{
    "token_type": "Bearer",
    "issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI...",
    "expires_in": 299,
    "scope": "foo bar"
}

The JWT token itself is the value under access_token. It will look something like this when parsed using
https://jwt.io/

Header:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "434980e1a98de643e022c67537807d4bd59e9859"
}

Body:

{
  "aud": "https://n.feide.no/datasources/coolapi",
  "iss": "https://auth.dataporten.no",
  "iat": 1669213259,
  "exp": 1669213559,
  "nbf": 1669213259,
  "client_id": "4cc40647-0d56-4de2-9f8f-975ff1ca55a8",
  "sub": "4cc40647-0d56-4de2-9f8f-975ff1ca55a8",
  "scope": "foo bar",
  "act": {
    "sub": "4cc40647-0d56-4de2-9f8f-975ff1ca55a8"
  }
}

User tokens

Clients related to users are a bit more difficult to generate. It requires you to do the whole browser-based authentication when you refer back and forth with Feide and the user logs in with 2FA.

Validating JWT token from feide

Per the Oatuh specification, information for various stuff related to the Oauth service can be found under the /.well-known/openid-configuration endpoint. For feide this is https://auth.dataporten.no/.well-known/openid-configuration. Here you can find the API where JWKs are defined (these contain public keys for validating signatures) and the issuer name for the Oauth service.

Verifying JWT token

The JWT token header contains a kid field. This is the ID for the keys used to sign the token. The public keys for Feide can be found at https://auth.dataporten.no/openid/jwks (As mentioned above, this can be inferred from the well-known endpoint). This endpoint exposes JWKS that define keys used for signing tokens (read more here https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets). The key with a matching kid value is the correct key to use for verifying the signature of the token.

Verifying issuer

It is important to verify the issuer to make sure that the token was actually created by someone you trust (in this case Feide).
To verify the issuer simply compare the iss claim given in the token itself with the issuer value you find in the well-known endpoint. The correct issuer value for a Feide issued token is https://auth.dataporten.no

Verifying audience

You should also make sure that the token is actually made specifically for your application. If you dont check this, then any other token generated by Feide for some other API can also be used to access your API. You should check that the aud claim in the token matches the audience value for your API (which in the case of this example is https://n.feide.no/datasources/coolapi).

Validate expiry time

Check the expiry time defined by the exp claim (UNIX timestamp). If this date is in the past, then the token has expired and should not be accepted.

Validate not-before

Check the time defined by the nbf claim (UNIX timestamp). This claim defines from which point in time the token is active. If the nbf value is in the future. then the token should not be accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants