What's the reasoning behind client_secret being provided as URL parameter when exchanging code for access_token? URL params have their weakness, such as often being stored in logs. Considering the exchange is done via the POST method, why is the secret not passed in the payload instead?
In most cases the secret is passed in the header and different implementations of OAuth have either a sha hashing to secure the request. Is there a specific provider that you are referring to?
Related
The access token and refresh token being in the header makes sense to me (from this answer), but I'm wondering the same thing for ID or Identity Tokens. Should those be:
Passed into the body of a post request?
Passed into some header (Authorization header is used by the access token)
Something else (I'm misunderstanding the purpose of the identity token)
A little more context, through SSO, I would like to take the claims of a user (present only in the Identity Token) and sign them up in my system. I can confirm I can do this easily by throwing the identity token into the body of the initial POST request (and validate the signature on the server), but wanted to check if I'm breaking some standard.
Thanks!
In principle you can pass the ID token assuming that:
the software that consumes and validates the ID token (Relying Party, RP) is part of the same application and security domain as the target system
the link between the RP and the target system is secure i.e. authentication of both parties and confidentiality of the communication is established
the target system indeed requires all of the information in the ID token, otherwise it would be better to apply minimal disclosure and pass only a subset of the information
Passing the ID token (or the subset of claims) in headers may work if the amount of data is relatively small, otherwise you may run into HTTP header length limitations. Using POST though has the downside of having to modify the application request in flight which may be harder and have side effects.
For completeness, the refresh token is never passed in a header and only sent to the Authorization Server / Provider by the RP in a POST request.
I'm taking a ASP.NET security course on OpenID Connect authorization code workflow with PKCE protection against code replay attack. There is one aspect of this process that I don't understand.
The process:
Relying client generates PKCE code_verifier, hash it into code_challenge, and send the user to the authorization server with the code_challenge as a query parameter.
The authorization server stores the code_challenge, issues authorization code, and redirects user back to client with the authorization code.
The client sends the authorization code along with the original code_verifier to exchange for tokens. The authorization server verifies that the code_verifier indeed hashes into code_challenge before issuing the token.
My question is step #3: since HTTP is stateless, how does the client know which code_verifier to send along with the authorization code? Is this code_verifier stored in a cookie on the user agent?
Using a cookie to store the "code verifier" appears to violate the spirit of the OpenId specification and just appears to be a big security hole.
In brief, the spec expects you to use S256 to hash the code verifier and the code verifier to be kept secret from eavesdropping or being guessed.
PKCE RFC Section 7.1 says the "The security model relies on the fact that the code verifier is not learned or guessed by the attacker. It is vitally important to adhere to this principle. As such, the code verifier has to be created in such a manner that it is cryptographically random and has high entropy that it is not practical for the attacker to guess."
... "The use of "S256" protects against disclosure of the "code_verifier" value to an attacker."
Now it does give an opening to using a cookie if you encrypt the "code verifier". Here is the what the spec says in
Section 7.2: "If the code challenge method is "plain" and the code challenge is to be returned inside authorization "code" to achieve a stateless server, it MUST be encrypted in such a manner that only the server can decrypt and extract it."
Well, OpenId specification has a solution and Microsoft has provided an implementation to follow. Refer to the "state" parameter in the OpenId specification when calling the "authorization" endpoint. The Identity Providers will return the value back to the caller.
The way it is done in .NET Core is using the "state" query string parameter whereby the value is a dictionary but protected (encrypted). For help look through the .NET core code and look at the method "BuildChallengeUrl" in "Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler"
Also, this Site has a great idea that works. It's a valid option.
PKCE was originally intended for mobile clients, which typically make only a single authorization request. But it is now recommended for all clients using the authorization code flow.
You're correct that if the client is a web application serving many users, the client application needs to associate the correct code_verifier with the authorization request.
As you say, the client could store the code_verifier in a cookie, so it will be sent to the client together with the authorization code.
Or it could store it server side on the client and stick the key in the state parameter to the authorization request. The state will be part of the call to the redirect_uri if passed along with the authorization request.
While learning how OAuth2 works, I cannot figure out why there is a separate step to retrieve Access Token?
A separate step means:
an extract HTTP request
passing Client Secret in the URL
I'd expect the Access Token to be generated in the "authorization" step, encrypted with using the Client Secret, and returned back when redirecting to the Callback URL. Then the client application would decrypt it and use it straight await without issuing an extra HTTP request.
I guess there are some reasons behind having an extra step, and I'm just not aware of them. I hope you can explain the reasons in your answer.
I'm assuming you're talking about the Authorization Code flow and not the Implicit flow, which does return a token directly.
The Authorization Code flow is designed to work with potentially unencrypted servers via a callback URL (this was designed years before Let's Encrypt and the relatively recent encrypt-everything push). Thus, the URL could be intercepted by any intermediate routers/proxies, and sending an access code as part of the callback URL in that environment is a Bad Idea.
So instead, the authorization code is sent. Then the client exchanges the authorization code along with its client secret for an access token. The authorization code can be intercepted, but in the Authorization Code flow, the client secret is actually secret and not known outside your server, so any intercepted authorization code is useless on its own.
These days, encryption is common and free, and unencrypted flows are strongly discouraged. The extra call remains part of the Authorization Code flow for historical reasons.
I'm trying to use OAuth. In this example the provider was Windows Live. The redirect returned this fragment. I have added line breaks before the ampersands to make it a bit easier to read.
access_token=EwAwA61DBAAUGCCXc8wU/zFu9QnLdZXy%2bYnElFkAAQcQQB3c7oVYQmQhyeTOiw1Tp5iA7cjrLJbEnvXVoDlu48mjv7WX35RRIK3L3miAQEUrn5ZVNRSUV8dqiZi4kIko93k2bODqpIY7r/nBRmnTTbInajZm0iF1FLjXaFJGzM/XJN4jZiZUtipwaNu66cSwbEhNVUWwZufHjE7SNDUQze3/ciGP9c%2bTampSUS23u%2bcuKrCbj8jPhFIj2Tbritf83YcVaXLJHpEYVz2a1GKHm8/TPot2MgcjD3yBOBd5b/QBImASEcdOpouiYIshi1Ddy20iYL1Jv6JRpPExeWd8q9sEfk4a%2btMHIPFJ%2bdy0e6X9nRMjlx%2bHP0FhUrOp4rADZgAACD41pKeivbukAAIVEy964MrcJsT6MwfHHf54bi9Nfhek/vARUG32qt1HId/3GgYDKcXblAk6I7qW%2boywDMa%2bck59pJms7/pEGqSkLtY%2b5M86%2bWvSG9bNAJWfQnKT1re5L4AxpbJ2J7JOw9md%2byBnt9I3hk%2bQGoY4ZoSMTtZ2QOxIH0rfgxVqrebUjAcCf1AIl7yBusgF1zJITPTBX1fLaEw48VjXjNygQq/N82%2baKZhU2ZMBTtUzgnod4SMrb/IOaQsDF6prVTihGHae6rbRL/Ul4C/au59NAijEBB9evjM6PxSMMpMmag6VTXdVeLeCBFitFwcjtRUH38yIuhPGalBnRTVHyLpm8cS06mrQM2n5j9XnMFBtQzEQEtdvtOsUkIgpVYmqZE38CEh9YAjJBxG1Xo9Pdz/DnHflfc3PWtO1xMZsP/krBMQFxQRTPBOiNv%2bH2DPF1TED35iW%2bS5VDbxjSur1dCVAPqv3Vbduia1moJ1AUYPm9P7%2bcY%2bIV3skuz4Dzk0zQPgU3h7N6zPwr0oi%2bNe548sk6Cqq/wbF7oV6ytuAc0fbedf49I7QA8x8BEruePwNWj2N6v2vJQqQm6fOC%2by7ZQSsJ9830hD6E5yp4YVht%2bux%2b8wu%2bDQ9x/VB6BXLNC%2b4Q2aIhDQmui/JMrgHXgo2okv9FywwVQdaSLXVasfrCVmqzDwGFhIC
&token_type=bearer
&expires_in=3600
&scope=wl.signin%20wl.emails
&user_id=AAAAAAAAAAAAAAAAAAAAAB1EauoOQMnYy_bu42gkx90
&state=STATE
and I parsed that into name value pairs applying decodeURIcomponent to each.
That gave me an access_token value of
EwAwA61DBAAUGCCXc8wU/zFu9QnLdZXy+YnElFkAAQcQQB3c7oVYQmQhyeTOiw1Tp5iA7cjrLJbEnvXVoDlu48mjv7WX35RRIK3L3miAQEUrn5ZVNRSUV8dqiZi4kIko93k2bODqpIY7r/nBRmnTTbInajZm0iF1FLjXaFJGzM/XJN4jZiZUtipwaNu66cSwbEhNVUWwZufHjE7SNDUQze3/ciGP9c+TampSUS23u+cuKrCbj8jPhFIj2Tbritf83YcVaXLJHpEYVz2a1GKHm8/TPot2MgcjD3yBOBd5b/QBImASEcdOpouiYIshi1Ddy20iYL1Jv6JRpPExeWd8q9sEfk4a+tMHIPFJ+dy0e6X9nRMjlx+HP0FhUrOp4rADZgAACD41pKeivbukAAIVEy964MrcJsT6MwfHHf54bi9Nfhek/vARUG32qt1HId/3GgYDKcXblAk6I7qW+oywDMa+ck59pJms7/pEGqSkLtY+5M86+WvSG9bNAJWfQnKT1re5L4AxpbJ2J7JOw9md+yBnt9I3hk+QGoY4ZoSMTtZ2QOxIH0rfgxVqrebUjAcCf1AIl7yBusgF1zJITPTBX1fLaEw48VjXjNygQq/N82+aKZhU2ZMBTtUzgnod4SMrb/IOaQsDF6prVTihGHae6rbRL/Ul4C/au59NAijEBB9evjM6PxSMMpMmag6VTXdVeLeCBFitFwcjtRUH38yIuhPGalBnRTVHyLpm8cS06mrQM2n5j9XnMFBtQzEQEtdvtOsUkIgpVYmqZE38CEh9YAjJBxG1Xo9Pdz/DnHflfc3PWtO1xMZsP/krBMQFxQRTPBOiNv+H2DPF1TED35iW+S5VDbxjSur1dCVAPqv3Vbduia1moJ1AUYPm9P7+cY+IV3skuz4Dzk0zQPgU3h7N6zPwr0oi+Ne548sk6Cqq/wbF7oV6ytuAc0fbedf49I7QA8x8BEruePwNWj2N6v2vJQqQm6fOC+y7ZQSsJ9830hD6E5yp4YVht+ux+8wu+DQ9x/VB6BXLNC+4Q2aIhDQmui/JMrgHXgo2okv9FywwVQdaSLXVasfrCVmqzDwGFhIC
As I understood it, at this point all I have to do is add a header Authorization: Bearertoken value but doing so for a route protected by `[Authorization] produces 401 Unauth with a type of Basic. This leaves me at a bit of a loss as to how to proceed.
Looking at the browser debug info I see
WWW-Authenticate: Bearer error=invalid_token
Does the token being invalid mean this is an inappropriate token or that it can't be parsed as a token?
Is my methodology so far correct?
Is there some kind of server configuration I need to make?
Suggested next steps?
The Access token looks a bit like Base64 but it's not valid base64, I checked. Just thinking about the nature of a token, it's probably just the claims encrypted with the token issuer's private key. If that were so you could check the integrity of a token by decrypting the token using the issuer's public key, but I can't see how to use this observation to figure out why I have a 401.
Update
This is the startup.cs code that introduces token checking
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
SaveToken = true,
TokenValidationParameters = tokenValidationParameters
});
and this auth0 web page cannot decode my token as a JWT token. Further reading suggests that a JWT token has three dot-separated sections each base64 encoded, and this is simply not present in the tokens I'm receiving from both Windows Live and Google (I've tested with both now).
So perhaps I need to restructure the token request if I want JWT tokens. More on this next episode. Feel free to write the next episode as an answer, if you know what I ought to be doing, endpoint and parameters etc. This other question What is the difference between id_token and access_token in Auth0 appears to be salient.
An access_token is what is says on the box - a token for access to a particular resource.
A JWT is an identity token. To get one of those, what I need to do is
use this endpoint: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
specify a response_type of id_token
Include a nonce
Specify a scope of openid.
But returning to the question of how to use an access token, you use it as a bearer token in the authorization header in a requestion to the provider's endpoint for the scope in question, eg wl.email
When an OAuth signed request is made to a Rails 3 app, I can see the OAuth consumer key in amongst the other values in request.headers["HTTP_AUTHORIZATION"]. What is a better way to access it? I'm using the OAuth gem.
According to this section in the OAuth specification, the Authorization header is the preferred way of sending OAuth protocol parameters. The specification does make allowances for sending protocol parameters in a form encoded body or in the request URI, if the request meets certain requirements.
To answer your question: parsing the Authorization header is a must for any OAuth provider. But you may also look for it (and other protocol parameters) amongst "normal" parameters, you will never find them in more than one place,