I have an ASP.NET MVC application that needs to integrate OpenID Connect authentication from a Private OpenID Connect (OIDC) Provider, and the flow has the following steps:
user click sign-in
it will redirect the user to the private OIDC site for authentication using the below HTTP GET
request:
after successful login in the private OIDC site, it will redirect back to my site and get the uri with a code
result showing as below:
then i will need to use the code
from the above and make an HTTP POST
call to the private ODIC token endpoint to get the access token for this user.
So, my questions #1
is: how to implement this in the c# asp.net app?
Also, I tried this in Postman "Get New Access Token", and I got the token.
as you can see after I supply all the parameters and click Request Token, it popup the login winnow, after successful sign in ,it shows the token
my questions #2
is: similar to question #1, is there anyway to implement this in c# asp.net app?
like in a asp.net mvc app, add a link-button with the url in the 1st image, when user clicks it will redirect it back to myapp with the code
, and then use this code to make HTTP POST call in the stpe3.
You can find an open source example of this on GitHub. The license of that is very permissive, and it's well documented. I've used it in various workshops and trainings, so most of the bugs have been worked out. I would advise you to dig into that. For completeness though, I'll describe the general process here, and use that as the basis for explaining.
Any Web application implementing the OpenID Connect code flow will include two parts:
The application that perform these two things is called a "client" or "relying party". The thing that this client communicates with using the OpenID Connect protocol is called an OpenID Connect Provider (OP) and is often also referred to as an Identity Provider (IdP).
The first part of the client implementation will show a view that contains a button. This button will be the typical "login" or "sign in" button. Note that this is optional, and the application may immediately redirect the user to the OP if it detects that the user doesn't have a session. Given your question above, however, this won't be the case for you, and the client will start by rendering a view that shows such a button. The view might look something like this:
<div>
@if(Session.Count == 0) {
<p>
This is a demo application to demonstrate the use for OAuth2
and OpenID Connect.
</p>
<p>
Pressing Sign In will redirect you to @ViewData["server_name"]
and authorize the application to access your profile info. The
data will only be used to demonstrate the possibilities of the
OpenID Connect protocol and will not be stored. Be sure to
revoke access when you are satisfied.
</p>
<div>
<a href="/login">Sign In</a>
</div>
} else {
// ...
}
</div>
This view would be rendered by a very basic controller that is wired up in the routing configuration established in Global.asax.cs
. When the sign in button is clicked, the OpenID Connect parts start. The controller that handles this request would simply redirect to the OP's authorization endpoint. This might look like this, in the most basic case:
public class LoginController : Controller
{
private static string start_oauth_endpoint = Helpers.Client.Instance.GetAuthnReqUrl();
public ActionResult Index()
{
return Redirect(start_oauth_endpoint);
}
}
The interesting part is how the authorization endpoint is obtained. This could be hard-coded, defined in Web.config
, or obtained from the metadata of the OP. In the example I referenced above, it fetches the OP's metadata on app start. This is done in AppConfig
located in the App_Start
directory of the Web app. This performs an HTTP GET request to the issuer ID (located in Web.config
) with /.well-known/openid-configuration
). The reason for fetching this metadata on app start rather than putting all of it in configuration is to reduce the coupling of the OP and client.
The redirection performed in the snipped above will have a few important query string parameters. Some of these will be known at design-time, and will be hard coded. Others will be configured in Web.config
. Some will be dynamically computed at run-time. These are listed below:
client_id
response_type
code
in your case.scope
openid
.redirect_uri
Other request parameters can also be sent. To help you figure out which to send, and the effect they have on the flow, checkout oauth.tools. This is like "Postman for OAuth and OpenID Connect". It's fantastic; you'll love it. There, you can form all sorts of OAuth and OpenID Connect flows with their various parameters.
Once this redirect is made to the OP, the user will authenticate. The user may also have to consent to the client's access to their protected resources. In any event, the OP will redirect the user to the callback after that. This is the second part of the implementation.
Here, we'll have a CallbackController
(or something along those lines). It will look like this (in its simplest form):
public class CallbackController : Controller
{
public ActionResult Index()
{
try
{
string responseString = Helpers.Client.Instance
.GetToken(Request.QueryString["code"]);
SaveDataToSession(responseString);
}
catch (Exception e)
{
Session["error"] = e.Message;
}
return Redirect("/");
}
}
The important part of this snippet is that it's obtaining the code
from the query string, and making an HTTP POST request to the OP's token endpoint (which was also located by parsing the OP's metadata). If this succeeds, it will save the response in the session for later use. The GetToken
method will look something like this:
public String GetToken(String code)
{
var values = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", client_id},
{ "client_secret", client_secret },
{ "code" , code },
{ "redirect_uri", redirect_uri}
};
HttpClient tokenClient = new HttpClient();
var content = new FormUrlEncodedContent(values);
var response = tokenClient.PostAsync(token_endpoint, content).Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
return responseContent.ReadAsStringAsync().Result;
}
throw new OAuthClientException("Token request failed with status code: " + response.StatusCode);
}
This will send the code to the OP and get an access token, ID token, and perhaps a refresh token back in exchange. The important parts of this code are:
grant_Type
is alway authorization_code
.client_id
in the request as was previously sent together with a secret in the client_secret
form element.In my example above, I redirect back to the default, HomeController
. Now, that if statement's else condition executes. In this, it can find the tokens:
<div>
@if(Session.Count == 0) {
// ...
} else {
@if(Session["id_token"] != null) {
<div>
ID Token:<br>
<pre>@Session["id_token"]</pre>
</div>
}
@if(Session["access_token"] != null) {
<div>
Access Token:<br>
<pre>@Session["access_token"]</pre>
</div>
}
@if(Session["refresh_token"] != null) {
<div>
Refresh Token:<br>
<pre>@Session["refresh_token"]</pre>
</div>
}
}
</div>
The example is more elaborate than this, but it hopefully gives you an idea. Go through that, check the README, and have fun learning more about OpenID Connect!